Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3350-Smart_table_unit_tests
This commit is contained in:
commit
320cc6c02c
|
@ -2,8 +2,7 @@ DROP PROCEDURE IF EXISTS `vn`.`item_getBalance`;
|
|||
|
||||
DELIMITER $$
|
||||
$$
|
||||
CREATE
|
||||
definer = root@`%` procedure `vn`.`item_getBalance`(IN vItemId int, IN vWarehouse int)
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`item_getBalance`(IN vItemId int, IN vWarehouse int)
|
||||
BEGIN
|
||||
DECLARE vDateInventory DATETIME;
|
||||
DECLARE vCurdate DATE DEFAULT CURDATE();
|
||||
|
@ -116,7 +115,7 @@ BEGIN
|
|||
s.id,
|
||||
st.`order`,
|
||||
ct.code,
|
||||
cl.id
|
||||
cb.claimFk
|
||||
FROM sale s
|
||||
JOIN ticket t ON t.id = s.ticketFk
|
||||
LEFT JOIN ticketState ts ON ts.ticket = t.id
|
||||
|
@ -132,6 +131,7 @@ BEGIN
|
|||
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
|
||||
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id
|
||||
LEFT JOIN claim cl ON cl.ticketFk = t.id
|
||||
LEFT JOIN claimBeginning cb ON cl.id = cb.claimFk AND s.id = cb.saleFk
|
||||
WHERE t.shipped >= vDateInventory
|
||||
AND s.itemFk = vItemId
|
||||
AND vWarehouse =t.warehouseFk
|
||||
|
@ -141,4 +141,3 @@ BEGIN
|
|||
END;
|
||||
$$
|
||||
DELIMITER ;
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE vn.payMethod CHANGE ibanRequiredForClients isIbanRequiredForClients tinyint(3) DEFAULT 0 NULL;
|
||||
ALTER TABLE vn.payMethod CHANGE ibanRequiredForSuppliers isIbanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL;
|
|
@ -217,7 +217,7 @@ UPDATE `vn`.`agencyMode` SET `web` = 1, `reportMail` = 'no-reply@gothamcity.com'
|
|||
|
||||
UPDATE `vn`.`agencyMode` SET `code` = 'refund' WHERE `id` = 23;
|
||||
|
||||
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `ibanRequiredForClients`, `ibanRequiredForSuppliers`)
|
||||
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `isIbanRequiredForClients`, `isIbanRequiredForSuppliers`)
|
||||
VALUES
|
||||
(1, NULL, 'PayMethod one', 0, 001, 0, 0),
|
||||
(2, NULL, 'PayMethod two', 10, 001, 0, 0),
|
||||
|
@ -806,7 +806,7 @@ INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `d
|
|||
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0),
|
||||
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0),
|
||||
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0),
|
||||
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 0),
|
||||
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1),
|
||||
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0),
|
||||
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0),
|
||||
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0),
|
||||
|
@ -1882,6 +1882,7 @@ INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `
|
|||
|
||||
INSERT INTO `postgresql`.`calendar_employee` (`business_id`, `calendar_state_id`, `date`)
|
||||
VALUES
|
||||
(1, 6, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -10 DAY), DATE_ADD(CURDATE(), INTERVAL 10 DAY))),
|
||||
(1106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -10 DAY), DATE_ADD(CURDATE(), INTERVAL 10 DAY))),
|
||||
(1106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -11 DAY), DATE_ADD(CURDATE(), INTERVAL 11 DAY))),
|
||||
(1106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -12 DAY), DATE_ADD(CURDATE(), INTERVAL 12 DAY))),
|
||||
|
|
|
@ -47,19 +47,20 @@ TABLES=(
|
|||
cplusSubjectOp
|
||||
cplusTaxBreak
|
||||
cplusTrascendency472
|
||||
pgc
|
||||
time
|
||||
claimResponsible
|
||||
claimReason
|
||||
claimRedelivery
|
||||
claimResult
|
||||
ticketUpdateAction
|
||||
state
|
||||
sample
|
||||
department
|
||||
component
|
||||
componentType
|
||||
continent
|
||||
department
|
||||
itemPackingType
|
||||
pgc
|
||||
sample
|
||||
state
|
||||
ticketUpdateAction
|
||||
time
|
||||
volumeConfig
|
||||
)
|
||||
dump_tables ${TABLES[@]}
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
color: $color-font;
|
||||
|
||||
&::placeholder {
|
||||
color: $color-font-bg;
|
||||
color: $color-font-bg-marginal;
|
||||
}
|
||||
&[type=time],
|
||||
&[type=date],
|
||||
|
@ -116,6 +116,7 @@
|
|||
&:active,
|
||||
&:valid {
|
||||
box-shadow: 0 0 0 40px $color-bg-panel inset;
|
||||
-webkit-text-fill-color: $color-primary-medium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +199,7 @@
|
|||
}
|
||||
&.standout {
|
||||
border-radius: 1px;
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
background-color: rgba(161, 161, 161, 0.1);
|
||||
padding: 0 12px;
|
||||
transition-property: background-color, color;
|
||||
transition-duration: 200ms;
|
||||
|
@ -208,6 +209,17 @@
|
|||
& > .underline {
|
||||
display: none;
|
||||
}
|
||||
& > .infix > .control > input {
|
||||
&:-internal-autofill-selected {
|
||||
&,
|
||||
&:hover,
|
||||
&:active,
|
||||
&:valid {
|
||||
box-shadow: 0 0 0 40px #474747 inset;
|
||||
-webkit-text-fill-color: $color-font-dark
|
||||
}
|
||||
}
|
||||
}
|
||||
& > .infix > .control > * {
|
||||
color: $color-font-dark;
|
||||
|
||||
|
@ -225,6 +237,17 @@
|
|||
background-color: $color-font-dark;
|
||||
|
||||
& > .container {
|
||||
& > .infix > .control > input {
|
||||
&:-internal-autofill-selected {
|
||||
&,
|
||||
&:hover,
|
||||
&:active,
|
||||
&:valid {
|
||||
box-shadow: 0 0 0 40px $color-font-dark inset;
|
||||
-webkit-text-fill-color: $color-font-bg
|
||||
}
|
||||
}
|
||||
}
|
||||
& > .infix > .control > * {
|
||||
color: $color-marginal;
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<div class="icons pre">
|
||||
<vn-icon
|
||||
icon="clear"
|
||||
ng-show="::$ctrl.clearDisabled != true"
|
||||
translate-attr="{title: 'Clear'}"
|
||||
ng-click="$ctrl.onClear($event)">
|
||||
</vn-icon>
|
||||
|
|
|
@ -85,6 +85,7 @@ ngModule.vnComponent('vnInputNumber', {
|
|||
min: '<?',
|
||||
max: '<?',
|
||||
step: '<?',
|
||||
displayControls: '<?'
|
||||
displayControls: '<?',
|
||||
clearDisabled: '<?'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,9 +23,12 @@ vn-layout {
|
|||
padding-right: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
& > .logo > img {
|
||||
height: 32px;
|
||||
display: block;
|
||||
& > .logo {
|
||||
outline: 0;
|
||||
& > img {
|
||||
height: 32px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
& > .main-title {
|
||||
font-size: 1.56rem;
|
||||
|
|
|
@ -193,6 +193,7 @@
|
|||
"Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
|
||||
"None": "Ninguno",
|
||||
"The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada",
|
||||
"Cannot add more than one '1/2 day vacation'": "No puedes añadir más de un 'Vacaciones 1/2 dia'",
|
||||
"This document already exists on this ticket": "Este documento ya existe en el ticket",
|
||||
"Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables",
|
||||
"You can't invoice tickets from multiple clients": "No puedes facturar tickets de multiples clientes",
|
||||
|
@ -212,5 +213,6 @@
|
|||
"You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito",
|
||||
"You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente",
|
||||
"The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'",
|
||||
"The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos"
|
||||
"The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos",
|
||||
"You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días"
|
||||
}
|
|
@ -57,8 +57,14 @@ module.exports = Self => {
|
|||
}
|
||||
}, myOptions);
|
||||
|
||||
const landedPlusWeek = new Date(ticket.landed);
|
||||
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
|
||||
const isClaimable = landedPlusWeek >= new Date();
|
||||
|
||||
if (ticket.isDeleted)
|
||||
throw new UserError(`You can't create a claim for a removed ticket`);
|
||||
if (!isClaimable)
|
||||
throw new UserError(`You can't create a claim from a ticket delivered more than seven days ago`);
|
||||
|
||||
const newClaim = await Self.create({
|
||||
ticketFk: ticketId,
|
||||
|
|
|
@ -1,31 +1,40 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('Claim createFromSales()', () => {
|
||||
const ticketId = 2;
|
||||
const ticketId = 16;
|
||||
const newSale = [{
|
||||
id: 3,
|
||||
instance: 0,
|
||||
quantity: 10
|
||||
}];
|
||||
const ctx = {
|
||||
req: {
|
||||
accessToken: {userId: 1},
|
||||
headers: {origin: 'localhost:5000'},
|
||||
__: () => {}
|
||||
}
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 1},
|
||||
headers: {origin: 'localhost:5000'},
|
||||
__: () => {}
|
||||
};
|
||||
|
||||
const ctx = {
|
||||
req: activeCtx
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new claim', async() => {
|
||||
const tx = await app.models.Claim.beginTransaction({});
|
||||
const tx = await models.Claim.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const claim = await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
|
||||
const claim = await models.Claim.createFromSales(ctx, ticketId, newSale, options);
|
||||
|
||||
expect(claim.ticketFk).toEqual(ticketId);
|
||||
|
||||
let claimBeginning = await app.models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
|
||||
let claimBeginning = await models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
|
||||
|
||||
expect(claimBeginning.saleFk).toEqual(newSale[0].id);
|
||||
expect(claimBeginning.quantity).toEqual(newSale[0].quantity);
|
||||
|
@ -37,17 +46,42 @@ describe('Claim createFromSales()', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should not be able to create a claim if exists that sale', async() => {
|
||||
const tx = await app.models.Claim.beginTransaction({});
|
||||
it('should not be able to create a claim for a ticket delivered more than seven days ago', async() => {
|
||||
const tx = await models.Claim.beginTransaction({});
|
||||
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
|
||||
const todayMinusEightDays = new Date();
|
||||
todayMinusEightDays.setDate(todayMinusEightDays.getDate() - 8);
|
||||
|
||||
await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
|
||||
const ticket = await models.Ticket.findById(ticketId, options);
|
||||
await ticket.updateAttribute('landed', todayMinusEightDays, options);
|
||||
|
||||
await models.Claim.createFromSales(ctx, ticketId, newSale, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.toString()).toContain(`You can't create a claim from a ticket delivered more than seven days ago`);
|
||||
});
|
||||
|
||||
it('should not be able to create a claim if exists that sale', async() => {
|
||||
const tx = await models.Claim.beginTransaction({});
|
||||
|
||||
let error;
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Claim.createFromSales(ctx, ticketId, newSale, options);
|
||||
|
||||
await models.Claim.createFromSales(ctx, ticketId, newSale, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
|
|
@ -138,7 +138,8 @@ module.exports = Self => {
|
|||
|
||||
function hasIban(err, done) {
|
||||
Self.app.models.PayMethod.findById(this.payMethodFk, (_, instance) => {
|
||||
if (instance && instance.ibanRequiredForClients && !this.iban)
|
||||
const isMissingIban = instance && instance.isIbanRequiredForClients && !this.iban;
|
||||
if (isMissingIban)
|
||||
err();
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
"outstandingDebt": {
|
||||
"type": "Number"
|
||||
},
|
||||
"ibanRequiredForClients": {
|
||||
"isIbanRequiredForClients": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ibanRequiredForSuppliers": {
|
||||
"isIbanRequiredForSuppliers": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
vn-acl="salesAssistant"
|
||||
ng-model="$ctrl.client.payMethodFk"
|
||||
data="paymethods"
|
||||
fields="['ibanRequiredForClients']"
|
||||
fields="['isIbanRequiredForClients']"
|
||||
initial-data="$ctrl.client.payMethod">
|
||||
</vn-autocomplete>
|
||||
<vn-input-number
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
<slot-body>
|
||||
<div class="attributes">
|
||||
<vn-label-value
|
||||
label="Phone"
|
||||
value="{{$ctrl.client.phone | phone}}">
|
||||
label="Pay method"
|
||||
value="{{$ctrl.client.payMethod.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Credit"
|
||||
|
|
|
@ -3,4 +3,5 @@ View consumer report: Ver informe de consumo
|
|||
From date: Fecha desde
|
||||
To date: Fecha hasta
|
||||
Go to user: Ir al usuario
|
||||
Client invoices list: Listado de facturas del cliente
|
||||
Client invoices list: Listado de facturas del cliente
|
||||
Pay method: Forma de pago
|
|
@ -39,6 +39,11 @@ module.exports = Self => {
|
|||
type: 'integer',
|
||||
description: 'The buyer of the item',
|
||||
},
|
||||
{
|
||||
arg: 'supplierFk',
|
||||
type: 'integer',
|
||||
description: 'The supplier of the item',
|
||||
},
|
||||
{
|
||||
arg: 'active',
|
||||
type: 'boolean',
|
||||
|
@ -49,6 +54,11 @@ module.exports = Self => {
|
|||
type: 'boolean',
|
||||
description: 'Whether the item is or not visible',
|
||||
},
|
||||
{
|
||||
arg: 'floramondo',
|
||||
type: 'boolean',
|
||||
description: 'Whether the item is or not floramondo',
|
||||
},
|
||||
{
|
||||
arg: 'typeFk',
|
||||
type: 'integer',
|
||||
|
@ -99,10 +109,14 @@ module.exports = Self => {
|
|||
return {'ic.id': value};
|
||||
case 'salesPersonFk':
|
||||
return {'it.workerFk': value};
|
||||
case 'supplierFk':
|
||||
return {'s.id': value};
|
||||
case 'code':
|
||||
return {'it.code': value};
|
||||
case 'active':
|
||||
return {'i.isActive': value};
|
||||
case 'floramondo':
|
||||
return {'i.isFloramondo': value};
|
||||
case 'visible':
|
||||
if (value)
|
||||
return {'v.visible': {gt: 0}};
|
||||
|
@ -172,7 +186,9 @@ module.exports = Self => {
|
|||
LEFT JOIN itemCategory ic ON ic.id = it.categoryFk
|
||||
LEFT JOIN itemType t ON t.id = i.typeFk
|
||||
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
|
||||
LEFT JOIN origin ori ON ori.id = i.originFk`
|
||||
LEFT JOIN origin ori ON ori.id = i.originFk
|
||||
LEFT JOIN entry e ON e.id = b.entryFk AND e.created >= DATE_SUB(CURDATE(),INTERVAL 1 YEAR)
|
||||
LEFT JOIN supplier s ON s.id = e.supplierFk`
|
||||
);
|
||||
|
||||
if (ctx.args.tags) {
|
||||
|
|
|
@ -113,6 +113,30 @@ describe('Buy latests buys filter()', () => {
|
|||
expect(results.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should return results matching "floramondo"', async() => {
|
||||
let ctx = {
|
||||
args: {
|
||||
floramondo: true
|
||||
}
|
||||
};
|
||||
|
||||
let results = await app.models.Buy.latestBuysFilter(ctx);
|
||||
|
||||
expect(results.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should return results matching "not floramondo"', async() => {
|
||||
let ctx = {
|
||||
args: {
|
||||
floramondo: false
|
||||
}
|
||||
};
|
||||
|
||||
let results = await app.models.Buy.latestBuysFilter(ctx);
|
||||
|
||||
expect(results.length).toBe(5);
|
||||
});
|
||||
|
||||
it('should return results matching "salesPersonFk"', async() => {
|
||||
let ctx = {
|
||||
args: {
|
||||
|
@ -136,5 +160,17 @@ describe('Buy latests buys filter()', () => {
|
|||
|
||||
expect(results.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should return results matching "supplierFk"', async() => {
|
||||
let ctx = {
|
||||
args: {
|
||||
supplierFk: 1
|
||||
}
|
||||
};
|
||||
|
||||
let results = await app.models.Buy.latestBuysFilter(ctx);
|
||||
|
||||
expect(results.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -46,6 +46,17 @@
|
|||
where="{role: {inq: ['logistic', 'buyer']}}"
|
||||
label="Buyer">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
label="Supplier"
|
||||
ng-model="filter.supplierFk"
|
||||
url="Suppliers"
|
||||
fields="['name','nickname']"
|
||||
search-function="{or: [{nickname: {like: '%'+ $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
<tpl-item>{{name}}: {{nickname}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
|
@ -58,6 +69,11 @@
|
|||
ng-model="filter.visible"
|
||||
triple-state="true">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
label="Is floramondo"
|
||||
ng-model="filter.floramondo"
|
||||
triple-state="true">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-pt-sm">
|
||||
<vn-one class="text-subtitle1" translate>
|
||||
|
@ -81,18 +97,21 @@
|
|||
on-change="itemTag.value = null">
|
||||
</vn-autocomplete>
|
||||
<vn-textfield
|
||||
ng-show="tag.selection.isFree !== false"
|
||||
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
|
||||
vn-id="text"
|
||||
label="Value"
|
||||
ng-model="itemTag.value">
|
||||
</vn-textfield>
|
||||
<vn-autocomplete
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
ng-show="tag.selection.isFree === false"
|
||||
url="{{$ctrl.getSourceTable(tag.selection)}}"
|
||||
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
|
||||
search-function="{value: $search}"
|
||||
label="Value"
|
||||
ng-model="itemTag.value"
|
||||
show-field="name"
|
||||
value-field="name">
|
||||
show-field="value"
|
||||
value-field="value"
|
||||
rule>
|
||||
</vn-autocomplete>
|
||||
<vn-icon-button
|
||||
vn-none
|
||||
|
|
|
@ -55,21 +55,9 @@ class Controller extends SearchPanel {
|
|||
this.$.filter = value;
|
||||
}
|
||||
|
||||
getSourceTable(selection) {
|
||||
if (!selection || selection.isFree === true)
|
||||
return null;
|
||||
|
||||
if (selection.sourceTable) {
|
||||
return ''
|
||||
+ selection.sourceTable.charAt(0).toUpperCase()
|
||||
+ selection.sourceTable.substring(1) + 's';
|
||||
} else if (selection.sourceTable == null)
|
||||
return `ItemTags/filterItemTags/${selection.id}`;
|
||||
}
|
||||
|
||||
removeField(index, field) {
|
||||
this.fieldFilters.splice(index, 1);
|
||||
this.$.filter[field] = undefined;
|
||||
delete this.$.filter[field];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,33 +12,48 @@ describe('Entry', () => {
|
|||
controller = $componentController('vnLatestBuysSearchPanel', {$element});
|
||||
}));
|
||||
|
||||
describe('getSourceTable()', () => {
|
||||
it(`should return null if there's no selection`, () => {
|
||||
let selection = null;
|
||||
let result = controller.getSourceTable(selection);
|
||||
describe('filter() setter', () => {
|
||||
it(`should set the tags property to the scope filter with an empty array`, () => {
|
||||
const expectedFilter = {
|
||||
tags: [{}]
|
||||
};
|
||||
controller.filter = null;
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(controller.filter).toEqual(expectedFilter);
|
||||
});
|
||||
|
||||
it(`should return null if there's a selection but its isFree property is truthy`, () => {
|
||||
let selection = {isFree: true};
|
||||
let result = controller.getSourceTable(selection);
|
||||
it(`should set the tags property to the scope filter with an array of tags`, () => {
|
||||
const expectedFilter = {
|
||||
description: 'My item',
|
||||
tags: [{}]
|
||||
};
|
||||
const expectedFieldFilter = [{
|
||||
info: {
|
||||
label: 'description',
|
||||
name: 'description',
|
||||
type: null
|
||||
},
|
||||
name: 'description',
|
||||
value: 'My item'
|
||||
}];
|
||||
controller.filter = {
|
||||
description: 'My item'
|
||||
};
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(controller.filter).toEqual(expectedFilter);
|
||||
expect(controller.fieldFilters).toEqual(expectedFieldFilter);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should return the formated sourceTable concatenated to a path`, () => {
|
||||
let selection = {sourceTable: 'hello guy'};
|
||||
let result = controller.getSourceTable(selection);
|
||||
describe('removeField()', () => {
|
||||
it(`should remove the description property from the fieldFilters and from the scope filter`, () => {
|
||||
const expectedFilter = {tags: [{}]};
|
||||
controller.filter = {description: 'My item'};
|
||||
|
||||
expect(result).toEqual('Hello guys');
|
||||
});
|
||||
controller.removeField(0, 'description');
|
||||
|
||||
it(`should return a path if there's no sourceTable and the selection has an id`, () => {
|
||||
let selection = {id: 99};
|
||||
let result = controller.getSourceTable(selection);
|
||||
|
||||
expect(result).toEqual(`ItemTags/filterItemTags/${selection.id}`);
|
||||
expect(controller.filter).toEqual(expectedFilter);
|
||||
expect(controller.fieldFilters).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,6 +4,8 @@ Freight value: Porte
|
|||
Commission value: Comisión
|
||||
Package value: Embalaje
|
||||
Is ignored: Ignorado
|
||||
Is visible: Visible
|
||||
Is floramondo: Floramondo
|
||||
Grouping price: Precio grouping
|
||||
Packing price: Precio packing
|
||||
Min price: Precio min
|
||||
|
|
|
@ -48,6 +48,11 @@ module.exports = Self => {
|
|||
type: 'integer',
|
||||
description: 'The buyer of the item',
|
||||
},
|
||||
{
|
||||
arg: 'supplierFk',
|
||||
type: 'integer',
|
||||
description: 'The supplier of the item',
|
||||
},
|
||||
{
|
||||
arg: 'description',
|
||||
type: 'string',
|
||||
|
@ -112,14 +117,15 @@ module.exports = Self => {
|
|||
case 'isActive':
|
||||
case 'typeFk':
|
||||
case 'isFloramondo':
|
||||
param = `i.${param}`;
|
||||
return {[param]: value};
|
||||
return {[`i.${param}`]: value};
|
||||
case 'multiplier':
|
||||
return {'i.stemMultiplier': value};
|
||||
case 'categoryFk':
|
||||
return {'ic.id': value};
|
||||
case 'buyerFk':
|
||||
return {'it.workerFk': value};
|
||||
case 'supplierFk':
|
||||
return {'s.id': value};
|
||||
case 'origin':
|
||||
return {'ori.code': value};
|
||||
case 'intrastat':
|
||||
|
@ -170,7 +176,9 @@ module.exports = Self => {
|
|||
LEFT JOIN producer pr ON pr.id = i.producerFk
|
||||
LEFT JOIN origin ori ON ori.id = i.originFk
|
||||
LEFT JOIN cache.last_buy lb ON lb.item_id = i.id AND lb.warehouse_id = it.warehouseFk
|
||||
LEFT JOIN vn.buy b ON b.id = lb.buy_id`
|
||||
LEFT JOIN buy b ON b.id = lb.buy_id
|
||||
LEFT JOIN entry e ON e.id = b.entryFk
|
||||
LEFT JOIN supplier s ON s.id = e.supplierFk`
|
||||
);
|
||||
|
||||
if (ctx.args.tags) {
|
||||
|
|
|
@ -70,8 +70,48 @@ describe('item filter()', () => {
|
|||
const ctx = {args: {filter: filter, isFloramondo: true}};
|
||||
const result = await models.Item.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(3);
|
||||
expect(result[0].id).toEqual(9);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return 2 result filtering by buyerFk', async() => {
|
||||
const tx = await models.Item.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
try {
|
||||
const filter = {};
|
||||
const ctx = {args: {filter: filter, buyerFk: 16}};
|
||||
const result = await models.Item.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(2);
|
||||
expect(result[0].id).toEqual(13);
|
||||
expect(result[0].id).toEqual(16);
|
||||
expect(result[1].id).toEqual(71);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return 2 result filtering by supplierFk', async() => {
|
||||
const tx = await models.Item.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
try {
|
||||
const filter = {};
|
||||
const ctx = {args: {filter: filter, supplierFk: 1}};
|
||||
const result = await models.Item.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(2);
|
||||
expect(result[0].id).toEqual(1);
|
||||
expect(result[1].id).toEqual(3);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
|
|
@ -36,4 +36,36 @@ describe('item getBalance()', () => {
|
|||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should show the claimFk only on the claimed item', async() => {
|
||||
const tx = await models.Item.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
try {
|
||||
const firstFilter = {
|
||||
where: {
|
||||
itemFk: 1,
|
||||
warehouseFk: 1
|
||||
}
|
||||
};
|
||||
|
||||
const secondFilter = {
|
||||
where: {
|
||||
itemFk: 2,
|
||||
warehouseFk: 1
|
||||
}
|
||||
};
|
||||
|
||||
const firstItemBalance = await models.Item.getBalance(firstFilter, options);
|
||||
const secondItemBalance = await models.Item.getBalance(secondFilter, options);
|
||||
|
||||
expect(firstItemBalance[9].claimFk).toEqual(null);
|
||||
expect(secondItemBalance[5].claimFk).toEqual(2);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -47,9 +47,14 @@ module.exports = Self => {
|
|||
const where = filter.where;
|
||||
if (where && where.value) {
|
||||
stmt.merge(conn.makeWhere({value: {like: `%${where.value}%`}}));
|
||||
stmt.merge(`
|
||||
ORDER BY value LIKE '${where.value}' DESC,
|
||||
value LIKE '${where.value}%' DESC`);
|
||||
|
||||
const orderStmt = new ParameterizedSQL(
|
||||
`ORDER BY value LIKE ? DESC,
|
||||
value LIKE ? DESC`, [
|
||||
where.value,
|
||||
`${where.value}%`
|
||||
]);
|
||||
ParameterizedSQL.append(stmt, orderStmt);
|
||||
}
|
||||
|
||||
stmt.merge(conn.makeLimit(filter));
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "ItemPackingType",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "itemPackingType"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"id": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -43,10 +43,21 @@
|
|||
ng-model="filter.buyerFk"
|
||||
url="Items/activeBuyers"
|
||||
show-field="nickname"
|
||||
search-function="{firstName: $search}"
|
||||
search-function="{nickname: {like: '%'+ $search +'%'}}"
|
||||
value-field="workerFk"
|
||||
label="Buyer">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
label="Supplier"
|
||||
ng-model="filter.supplierFk"
|
||||
url="Suppliers"
|
||||
fields="['name','nickname']"
|
||||
search-function="{or: [{nickname: {like: '%'+ $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
|
||||
show-field="name"
|
||||
value-field="id">
|
||||
<tpl-item>{{name}}: {{nickname}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-pt-sm">
|
||||
<vn-one class="text-subtitle1" translate>
|
||||
|
@ -72,19 +83,21 @@
|
|||
</vn-autocomplete>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
ng-show="tag.selection.isFree !== false"
|
||||
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
|
||||
vn-id="text"
|
||||
label="Value"
|
||||
ng-model="itemTag.value">
|
||||
</vn-textfield>
|
||||
<vn-autocomplete
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
ng-show="tag.selection.isFree === false"
|
||||
url="{{$ctrl.getSourceTable(tag.selection)}}"
|
||||
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
|
||||
search-function="{value: $search}"
|
||||
label="Value"
|
||||
ng-model="itemTag.value"
|
||||
show-field="name"
|
||||
value-field="name">
|
||||
show-field="value"
|
||||
value-field="value"
|
||||
rule>
|
||||
</vn-autocomplete>
|
||||
<vn-icon-button
|
||||
vn-none
|
||||
|
|
|
@ -55,21 +55,9 @@ class Controller extends SearchPanel {
|
|||
this.$.filter = value;
|
||||
}
|
||||
|
||||
getSourceTable(selection) {
|
||||
if (!selection || selection.isFree === true)
|
||||
return null;
|
||||
|
||||
if (selection.sourceTable) {
|
||||
return ''
|
||||
+ selection.sourceTable.charAt(0).toUpperCase()
|
||||
+ selection.sourceTable.substring(1) + 's';
|
||||
} else if (selection.sourceTable == null)
|
||||
return `ItemTags/filterItemTags/${selection.id}`;
|
||||
}
|
||||
|
||||
removeField(index, field) {
|
||||
this.fieldFilters.splice(index, 1);
|
||||
this.$.filter[field] = undefined;
|
||||
delete this.$.filter[field];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,33 +12,48 @@ describe('Item', () => {
|
|||
controller = $componentController('vnItemSearchPanel', {$element});
|
||||
}));
|
||||
|
||||
describe('getSourceTable()', () => {
|
||||
it(`should return null if there's no selection`, () => {
|
||||
let selection = null;
|
||||
let result = controller.getSourceTable(selection);
|
||||
describe('filter() setter', () => {
|
||||
it(`should set the tags property to the scope filter with an empty array`, () => {
|
||||
const expectedFilter = {
|
||||
tags: [{}]
|
||||
};
|
||||
controller.filter = null;
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(controller.filter).toEqual(expectedFilter);
|
||||
});
|
||||
|
||||
it(`should return null if there's a selection but its isFree property is truthy`, () => {
|
||||
let selection = {isFree: true};
|
||||
let result = controller.getSourceTable(selection);
|
||||
it(`should set the tags property to the scope filter with an array of tags`, () => {
|
||||
const expectedFilter = {
|
||||
description: 'My item',
|
||||
tags: [{}]
|
||||
};
|
||||
const expectedFieldFilter = [{
|
||||
info: {
|
||||
label: 'description',
|
||||
name: 'description',
|
||||
type: null
|
||||
},
|
||||
name: 'description',
|
||||
value: 'My item'
|
||||
}];
|
||||
controller.filter = {
|
||||
description: 'My item'
|
||||
};
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(controller.filter).toEqual(expectedFilter);
|
||||
expect(controller.fieldFilters).toEqual(expectedFieldFilter);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should return the formated sourceTable concatenated to a path`, () => {
|
||||
let selection = {sourceTable: 'hello guy'};
|
||||
let result = controller.getSourceTable(selection);
|
||||
describe('removeField()', () => {
|
||||
it(`should remove the description property from the fieldFilters and from the scope filter`, () => {
|
||||
const expectedFilter = {tags: [{}]};
|
||||
controller.filter = {description: 'My item'};
|
||||
|
||||
expect(result).toEqual('Hello guys');
|
||||
});
|
||||
controller.removeField(0, 'description');
|
||||
|
||||
it(`should return a path if there's no sourceTable and the selection has an id`, () => {
|
||||
let selection = {id: 99};
|
||||
let result = controller.getSourceTable(selection);
|
||||
|
||||
expect(result).toEqual(`ItemTags/filterItemTags/${selection.id}`);
|
||||
expect(controller.filter).toEqual(expectedFilter);
|
||||
expect(controller.fieldFilters).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -79,8 +79,9 @@ module.exports = Self => {
|
|||
const payMethod = await Self.app.models.PayMethod.findById(this.payMethodFk);
|
||||
const supplierAccount = await Self.app.models.SupplierAccount.findOne({where: {supplierFk: this.id}});
|
||||
const hasIban = supplierAccount && supplierAccount.iban;
|
||||
const isMissingIban = payMethod && payMethod.isIbanRequiredForSuppliers && !hasIban;
|
||||
|
||||
if (payMethod && payMethod.ibanRequiredForSuppliers && !hasIban)
|
||||
if (isMissingIban)
|
||||
err();
|
||||
|
||||
done();
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
vn-acl="salesAssistant"
|
||||
ng-model="$ctrl.supplier.payMethodFk"
|
||||
data="paymethods"
|
||||
fields="['ibanRequiredForSuppliers']"
|
||||
fields="['isIbanRequiredForSuppliers']"
|
||||
initial-data="$ctrl.supplier.payMethod">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
|
|
|
@ -153,7 +153,8 @@
|
|||
<vn-input-number class="dense"
|
||||
vn-focus
|
||||
ng-model="sale.quantity"
|
||||
on-change="$ctrl.changeQuantity(sale)">
|
||||
on-change="$ctrl.changeQuantity(sale)"
|
||||
clear-disabled="true">
|
||||
</vn-input-number>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
|
@ -177,7 +178,8 @@
|
|||
<vn-textfield class="dense" vn-focus
|
||||
vn-id="concept"
|
||||
ng-model="sale.concept"
|
||||
on-change="$ctrl.updateConcept(sale)">
|
||||
on-change="$ctrl.updateConcept(sale)"
|
||||
clear-disabled="true">
|
||||
</vn-textfield>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
|
@ -251,6 +253,7 @@
|
|||
ng-model="$ctrl.edit.price"
|
||||
step="0.01"
|
||||
on-change="$ctrl.updatePrice()"
|
||||
clear-disabled="true"
|
||||
suffix="€">
|
||||
</vn-input-number>
|
||||
<div class="simulator">
|
||||
|
@ -283,6 +286,7 @@
|
|||
label="Discount"
|
||||
ng-model="$ctrl.edit.discount"
|
||||
on-change="$ctrl.changeDiscount()"
|
||||
clear-disabled="true"
|
||||
suffix="%">
|
||||
</vn-input-number>
|
||||
<div class="simulator">
|
||||
|
@ -311,6 +315,7 @@
|
|||
label="Discount"
|
||||
ng-model="$ctrl.edit.discount"
|
||||
on-change="$ctrl.changeMultipleDiscount()"
|
||||
clear-disabled="true"
|
||||
suffix="%">
|
||||
</vn-input-number>
|
||||
</div>
|
||||
|
@ -469,7 +474,8 @@
|
|||
</vn-item>
|
||||
<vn-item translate
|
||||
name="claim"
|
||||
ng-click="$ctrl.createClaim()">
|
||||
ng-click="$ctrl.createClaim()"
|
||||
ng-if="$ctrl.isClaimable">
|
||||
Add claim
|
||||
</vn-item>
|
||||
<vn-item translate
|
||||
|
|
|
@ -35,6 +35,16 @@ class Controller extends Section {
|
|||
return ticketState && ticketState.state.code;
|
||||
}
|
||||
|
||||
get isClaimable() {
|
||||
if (this.ticket) {
|
||||
const landedPlusWeek = new Date(this.ticket.landed);
|
||||
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
|
||||
|
||||
return landedPlusWeek >= new Date();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getSaleTotal(sale) {
|
||||
if (sale.quantity == null || sale.price == null)
|
||||
return null;
|
||||
|
|
|
@ -15,7 +15,8 @@ describe('Ticket', () => {
|
|||
const ticket = {
|
||||
id: 1,
|
||||
clientFk: 1101,
|
||||
shipped: 1,
|
||||
shipped: new Date(),
|
||||
landed: new Date(),
|
||||
created: new Date(),
|
||||
client: {salesPersonFk: 1},
|
||||
address: {mobile: 111111111}
|
||||
|
@ -74,6 +75,25 @@ describe('Ticket', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('isClaimable() getter', () => {
|
||||
it('should return true for a ticket delivered less than seven days ago', () => {
|
||||
const result = controller.isClaimable;
|
||||
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false for a ticket delivered more than seven days ago', () => {
|
||||
const ticket = controller.ticket;
|
||||
const landedMinusEightDays = new Date(ticket.landed);
|
||||
landedMinusEightDays.setDate(landedMinusEightDays.getDate() - 8);
|
||||
ticket.landed = landedMinusEightDays;
|
||||
|
||||
const result = controller.isClaimable;
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSaleTotal()', () => {
|
||||
it('should return the sale total amount', () => {
|
||||
const sale = {
|
||||
|
|
|
@ -65,6 +65,23 @@ module.exports = Self => {
|
|||
if (args.dated < labour.started || (labour.ended != null && args.dated > labour.ended))
|
||||
throw new UserError(`The contract was not active during the selected date`);
|
||||
|
||||
const result = await Self.rawSql(
|
||||
`SELECT COUNT(*) halfHolidayCounter
|
||||
FROM vn.calendar c
|
||||
JOIN postgresql.business b ON b.business_id = c.businessFk
|
||||
JOIN postgresql.profile p ON p.profile_id = b.client_id
|
||||
JOIN vn.person pe ON pe.id = p.person_id
|
||||
WHERE c.dayOffTypeFk = 6
|
||||
AND pe.workerFk = ?
|
||||
AND c.dated BETWEEN util.firstDayOfYear(CURDATE())
|
||||
AND LAST_DAY(DATE_ADD(NOW(), INTERVAL 12-MONTH(NOW()) MONTH))`, [args.id]);
|
||||
|
||||
const hasHalfHoliday = result[0].halfHolidayCounter > 0;
|
||||
const isHalfHoliday = args.absenceTypeId == 6;
|
||||
|
||||
if (isHalfHoliday && hasHalfHoliday)
|
||||
throw new UserError(`Cannot add more than one '1/2 day vacation'`);
|
||||
|
||||
const absence = await models.Calendar.create({
|
||||
businessFk: labour.businessFk,
|
||||
dayOffTypeFk: args.absenceTypeId,
|
||||
|
|
|
@ -74,4 +74,32 @@ describe('Worker createAbsence()', () => {
|
|||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it(`should throw an error when adding a "Half holiday" absence if there's already one`, async() => {
|
||||
const ctx = {
|
||||
req: {accessToken: {userId: 19}},
|
||||
args: {
|
||||
id: 1,
|
||||
businessFk: 1,
|
||||
absenceTypeId: 6,
|
||||
dated: new Date()
|
||||
}
|
||||
};
|
||||
|
||||
const tx = await app.models.Calendar.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await app.models.Worker.createAbsence(ctx, workerId, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error.message).toEqual(`Cannot add more than one '1/2 day vacation'`);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue