Merge branch 'dev' into 4588-campo-nota-ultimo-usuario
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alexandre Riera 2023-01-03 13:51:28 +00:00
commit 6ff17f0af0
54 changed files with 535 additions and 476 deletions

17
CHANGELOG.md Normal file
View File

@ -0,0 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2302.01] - 2023-01-12
### Added
- [General](Inicio) Permite recuperar la contraseña
### Changed
### Removed
- [Tickets](Control clientes) Eliminada sección

View File

@ -29,8 +29,12 @@ module.exports = Self => {
filter = mergeFilters(filter, {where});
const stmt = new ParameterizedSQL(
`SELECT * FROM campaign`);
`SELECT * FROM (`);
stmt.merge('SELECT * FROM campaign');
stmt.merge(conn.makeWhere(filter.where));
stmt.merge('ORDER BY dated ASC');
stmt.merge('LIMIT 10000000000000000000');
stmt.merge(') sub');
stmt.merge('GROUP BY code');
stmt.merge(conn.makePagination(filter));

View File

@ -1,4 +1,4 @@
INSERT INTO `util`.`notification` (id, name, description) VALUES(3, 'book-entries-imported-incorrectly', 'accounting entries exported incorrectly');
INSERT INTO `util`.`notificationAcl` (notificationFk, roleFk) VALUES(3, 5);
INSERT INTO `util`.`notificationSubscription` (notificationFk, userFk) VALUES(3, 19663);
INSERT IGNORE INTO `util`.`notificationSubscription` (notificationFk, userFk) VALUES(3, 19663);

View File

@ -0,0 +1,7 @@
CREATE TABLE `vn`.`stateI18n` (
`stateFk` tinyint(3) unsigned NOT NULL,
`lang` char(2) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`stateFk`, `lang`),
CONSTRAINT `stateI18n_state_id` FOREIGN KEY (`stateFk`) REFERENCES `vn`.`state` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

View File

@ -0,0 +1,73 @@
INSERT INTO
`vn`.`stateI18n` (`stateFk`, `lang`, `name`)
VALUES
(1, 'en', 'Fix'),
(1, 'es', 'Arreglar'),
(2, 'en', 'Free'),
(2, 'es', 'Libre'),
(3, 'en', 'OK'),
(3, 'es', 'OK'),
(4, 'en', 'Printed'),
(4, 'es', 'Impreso'),
(5, 'en', 'Preparation'),
(5, 'es', 'Preparación'),
(6, 'en', 'In Review'),
(6, 'es', 'En Revisión'),
(7, 'en', 'Unfinished'),
(7, 'es', 'Sin Acabar'),
(8, 'en', 'Reviewed'),
(8, 'es', 'Revisado'),
(9, 'en', 'Fitting'),
(9, 'es', 'Encajando'),
(10, 'en', 'Fitted'),
(10, 'es', 'Encajado'),
(11, 'en', 'Billed'),
(11, 'es', 'Facturado'),
(12, 'en', 'Blocked'),
(12, 'es', 'Bloqueado'),
(13, 'en', 'In Delivery'),
(13, 'es', 'En Reparto'),
(14, 'en', 'Prepared'),
(14, 'es', 'Preparado'),
(15, 'en', 'Pending Collection'),
(15, 'es', 'Pendiente de Recogida'),
(16, 'en', 'Delivered'),
(16, 'es', 'Entregado'),
(20, 'en', 'Assigned'),
(20, 'es', 'Asignado'),
(21, 'en', 'Returned'),
(21, 'es', 'Retornado'),
(22, 'en', 'Pending to extend'),
(22, 'es', 'Pendiente ampliar'),
(23, 'en', 'URGENT'),
(23, 'es', 'URGENTE'),
(24, 'en', 'Chained'),
(24, 'es', 'Encadenado'),
(25, 'en', 'Shipping'),
(25, 'es', 'Embarcando'),
(26, 'en', 'Preparation'),
(26, 'es', 'Preparación previa'),
(27, 'en', 'Assisted preparation'),
(27, 'es', 'Preparación asistida'),
(28, 'en', 'Preparation OK'),
(28, 'es', 'Previa OK'),
(29, 'en', 'Preparation Printed'),
(29, 'es', 'Previa Impreso'),
(30, 'en', 'Shipped'),
(30, 'es', 'Embarcado'),
(31, 'en', 'Stowaway printed'),
(31, 'es', 'Polizón Impreso'),
(32, 'en', 'Stowaway OK'),
(32, 'es', 'Polizón OK'),
(33, 'en', 'Auto_Printed'),
(33, 'es', 'Auto_Impreso'),
(34, 'en', 'Pending payment'),
(34, 'es', 'Pendiente de pago'),
(35, 'en', 'Half-Embedded'),
(35, 'es', 'Semi-Encajado'),
(36, 'en', 'Preparation Reviewing'),
(36, 'es', 'Previa Revisando'),
(37, 'en', 'Preparation Reviewed'),
(37, 'es', 'Previa Revisado'),
(38, 'en', 'Preparation Chamber'),
(38, 'es', 'Preparación Cámara');

View File

@ -0,0 +1,2 @@
DELETE FROM `salix`.`ACL` WHERE model="SaleChecked";
DROP TABLE IF EXISTS `vn`.`saleChecked`;

View File

@ -1335,9 +1335,9 @@ INSERT INTO `vn`.`itemTypeTag`(`id`, `itemTypeFk`, `tagFk`, `priority`)
CALL `vn`.`itemRefreshTags`(NULL);
INSERT INTO `vn`.`itemLog` (`id`, `originFk`, `userFk`, `action`, `description`)
INSERT INTO `vn`.`itemLog` (`id`, `originFk`, `userFk`, `action`, `description`, `changedModel`, `oldInstance`, `newInstance`, `changedModelId`, `changedModelValue`)
VALUES
('1', '1', '1', 'insert', 'We made a change!');
('1', '1', '1', 'insert', 'We made a change!', 'Item', '{}', '{}', 1, '1');
INSERT INTO `vn`.`recovery`(`id`, `clientFk`, `started`, `finished`, `amount`, `period`)
VALUES
@ -2652,7 +2652,7 @@ INSERT INTO `vn`.`mdbVersion` (`app`, `branchFk`, `version`)
INSERT INTO `vn`.`accountingConfig` (`id`, `minDate`, `maxDate`)
VALUES
(1, '2022-01-01', '2023-01-01');
(1, CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR)), '-01-01'), CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR)), '-01-01'));
INSERT INTO `vn`.`saleGroup` (`userFk`, `parkingFk`, `sectorFk`)
@ -2754,6 +2754,7 @@ INSERT INTO `vn`.`ticketLog` (`id`, `originFk`, `userFk`, `action`, `changedMode
VALUES
(1, 1, 9, 'insert', 'Ticket', '{}', '{"clientFk":1, "nickname": "Bat cave"}', 1);
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('lilium', 'dev', 'http://localhost:8080/#/'),

View File

@ -30,7 +30,7 @@ export default {
firstModuleRemovePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="remove_circle"]'
},
recoverPassword: {
recoverPasswordButton: 'vn-login a[ui-sref="recoverPassword"]',
recoverPasswordButton: 'vn-login a[ui-sref="recover-password"]',
email: 'vn-recover-password vn-textfield[ng-model="$ctrl.email"]',
sendEmailButton: 'vn-recover-password vn-submit',
},
@ -311,10 +311,12 @@ export default {
firstMandateText: 'vn-client-mandate vn-card vn-table vn-tbody > vn-tr'
},
clientLog: {
lastModificationPreviousValue: 'vn-client-log vn-table vn-td.before',
lastModificationCurrentValue: 'vn-client-log vn-table vn-td.after',
penultimateModificationPreviousValue: 'vn-client-log vn-table vn-tr:nth-child(2) vn-td.before',
penultimateModificationCurrentValue: 'vn-client-log vn-table vn-tr:nth-child(2) vn-td.after'
lastModificationPreviousValue: 'vn-client-log vn-tr table tr td.before',
lastModificationCurrentValue: 'vn-client-log vn-tr table tr td.after',
namePreviousValue: 'vn-client-log vn-tr table tr:nth-child(1) td.before',
nameCurrentValue: 'vn-client-log vn-tr table tr:nth-child(1) td.after',
activePreviousValue: 'vn-client-log vn-tr:nth-child(2) table tr:nth-child(2) td.before',
activeCurrentValue: 'vn-client-log vn-tr:nth-child(2) table tr:nth-child(2) td.after'
},
clientBalance: {
@ -518,7 +520,7 @@ export default {
},
itemLog: {
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) > vn-td > vn-one:nth-child(3) > div span:nth-child(2)',
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(3) td.after',
},
ticketSummary: {
header: 'vn-ticket-summary > vn-card > h5',
@ -711,9 +713,10 @@ export default {
ticketLog: {
firstTD: 'vn-ticket-log vn-table vn-td:nth-child(1)',
logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]',
firstLogEntry: 'vn-ticket-log vn-data-viewer vn-tbody vn-tr',
changes: 'vn-ticket-log vn-data-viewer vn-tbody > vn-tr > vn-td:nth-child(7)',
id: 'vn-ticket-log vn-tr:nth-child(1) vn-one:nth-child(1) span'
user: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(2)',
action: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(4)',
changes: 'vn-ticket-log vn-data-viewer vn-tbody vn-tr table tr:nth-child(2) td.after',
id: 'vn-ticket-log vn-tr:nth-child(1) table tr:nth-child(1) td.before'
},
ticketService: {
addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button',
@ -1123,7 +1126,7 @@ export default {
undoChanges: 'vn-travel-basic-data vn-button[label="Undo changes"]'
},
travelLog: {
firstLogFirstTD: 'vn-travel-log vn-tbody > vn-tr > vn-td:nth-child(1) > div'
firstLogFirstTD: 'vn-travel-log vn-tbody > vn-tr > vn-td:nth-child(5)'
},
travelThermograph: {
add: 'vn-travel-thermograph-index vn-float-button[icon="add"]',

View File

@ -1,8 +1,7 @@
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
// https://redmine.verdnatura.es/issues/4995 fix login
xdescribe('RecoverPassword path', async() => {
describe('RecoverPassword path', async() => {
let browser;
let page;
@ -11,7 +10,7 @@ xdescribe('RecoverPassword path', async() => {
page = browser.page;
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
await page.waitForState('recoverPassword');
await page.waitForState('recover-password');
});
afterAll(async() => {

View File

@ -67,22 +67,22 @@ describe('Client Edit web access path', () => {
});
it(`should confirm the last log shows the updated client name and no modifications on active checkbox`, async() => {
let lastModificationPreviousValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
let lastModificationCurrentValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
let namePreviousValue = await page
.waitToGetProperty(selectors.clientLog.namePreviousValue, 'innerText');
let nameCurrentValue = await page
.waitToGetProperty(selectors.clientLog.nameCurrentValue, 'innerText');
expect(lastModificationPreviousValue).toEqual('name MaxEisenhardt active false');
expect(lastModificationCurrentValue).toEqual('name Legion active false');
expect(namePreviousValue).toEqual('MaxEisenhardt');
expect(nameCurrentValue).toEqual('Legion');
});
it(`should confirm the penultimate log shows the updated active and no modifications on client name`, async() => {
let penultimateModificationPreviousValue = await page
.waitToGetProperty(selectors.clientLog.penultimateModificationPreviousValue, 'innerText');
let penultimateModificationCurrentValue = await page
.waitToGetProperty(selectors.clientLog.penultimateModificationCurrentValue, 'innerText');
let activePreviousValue = await page
.waitToGetProperty(selectors.clientLog.activePreviousValue, 'innerText');
let activeCurrentValue = await page
.waitToGetProperty(selectors.clientLog.activeCurrentValue, 'innerText');
expect(penultimateModificationPreviousValue).toEqual('name MaxEisenhardt active true');
expect(penultimateModificationCurrentValue).toEqual('name MaxEisenhardt active false');
expect(activePreviousValue).toEqual('✓');
expect(activeCurrentValue).toEqual('✗');
});
});

View File

@ -43,7 +43,7 @@ describe('Client log path', () => {
let lastModificationCurrentValue = await page.
waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
expect(lastModificationPreviousValue).toEqual('name DavidCharlesHaller');
expect(lastModificationCurrentValue).toEqual('name this is a test');
expect(lastModificationPreviousValue).toEqual('DavidCharlesHaller');
expect(lastModificationCurrentValue).toEqual('this is a test');
});
});

View File

@ -58,6 +58,7 @@ describe('Worker time control path', () => {
});
it(`should return error when insert 'out' of first entry`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
@ -68,6 +69,7 @@ describe('Worker time control path', () => {
});
it(`should insert 'in' monday`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
@ -78,6 +80,7 @@ describe('Worker time control path', () => {
});
it(`should insert 'out' monday`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, fourPm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
@ -88,11 +91,13 @@ describe('Worker time control path', () => {
});
it(`should check Hank Pym worked 8:20 hours`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '08:20 h.');
await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '08:20 h.');
});
it('should remove first entry of monday', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitForTextInElement(selectors.workerTimeControl.firstEntryOfMonday, eightAm);
await page.waitForTextInElement(selectors.workerTimeControl.secondEntryOfMonday, fourPm);
await page.waitToClick(selectors.workerTimeControl.firstEntryOfMondayDelete);
@ -103,6 +108,7 @@ describe('Worker time control path', () => {
});
it(`should be the 'out' the first entry of monday`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
expect(result).toEqual(fourPm);

View File

@ -32,14 +32,17 @@ describe('Ticket expeditions and log path', () => {
it(`should confirm the expedition deleted is shown now in the ticket log`, async() => {
await page.accessToSection('ticket.card.log');
const firstLogEntry = await page
.waitToGetProperty(selectors.ticketLog.firstLogEntry, 'innerText');
const user = await page
.waitToGetProperty(selectors.ticketLog.user, 'innerText');
const action = await page
.waitToGetProperty(selectors.ticketLog.action, 'innerText');
const id = await page
.waitToGetProperty(selectors.ticketLog.id, 'innerText');
expect(firstLogEntry).toContain('production');
expect(firstLogEntry).toContain('Deletes');
expect(user).toContain('production');
expect(action).toContain('Deletes');
expect(id).toEqual('2');
});
});

View File

@ -55,6 +55,6 @@ describe('Ticket log path', () => {
const result = await page.waitToGetProperty(selectors.ticketLog.firstTD, 'innerText');
expect(result.length).toBeGreaterThan('20');
expect(result.length).toBeGreaterThan('15');
});
});

View File

@ -70,8 +70,8 @@ describe('Supplier basic data path', () => {
});
it('should check the changes have been recorded', async() => {
const result = await page.waitToGetProperty('#newInstance:nth-child(3)', 'innerText');
const result = await page.waitToGetProperty('vn-tr table tr:nth-child(3) td.after', 'innerText');
expect(result).toEqual('note Some notes');
expect(result).toEqual('Some notes');
});
});

View File

@ -24,7 +24,7 @@ export default class Auth {
initialize() {
let criteria = {
to: state => {
const outLayout = ['login', 'recoverPassword', 'resetPassword'];
const outLayout = ['login', 'recover-password', 'reset-password'];
return !outLayout.some(ol => ol == state.name);
}
};

View File

@ -1,9 +1,3 @@
<vn-layout
ng-if="$ctrl.showLayout">
</vn-layout>
<ui-view
name="login"
ng-if="!$ctrl.showLayout">
</ui-view>
<ui-view></ui-view>
<vn-snackbar vn-id="snackbar"></vn-snackbar>
<vn-debug-info></vn-debug-info>

View File

@ -19,12 +19,6 @@ export default class App extends Component {
this.vnApp.logger = this;
}
get showLayout() {
const state = this.$state.current.name || this.$location.$$path.substring(1).replace('/', '.');
const outLayout = ['login', 'recoverPassword', 'resetPassword', 'reset-password'];
return state && !outLayout.some(ol => ol == state);
}
$onDestroy() {
this.deregisterCallback();
this.vnApp.logger = null;

View File

@ -5,10 +5,10 @@ import './descriptor-popover';
import './home/home';
import './layout';
import './left-menu/left-menu';
import './login/index';
import './login/login';
import './login/recover-password';
import './login/reset-password';
import './login';
import './outLayout';
import './recover-password';
import './reset-password';
import './module-card';
import './module-main';
import './side-menu/side-menu';

View File

@ -3,6 +3,8 @@
url="{{$ctrl.url}}"
filter="$ctrl.filter"
link="{originFk: $ctrl.originId}"
where="{changedModel: $ctrl.changedModel,
changedModelId: $ctrl.changedModelId}"
data="$ctrl.logs"
limit="20"
auto-load="true">
@ -13,81 +15,53 @@
<vn-thead>
<vn-tr>
<vn-th field="creationDate">Date</vn-th>
<vn-th field="userFk" class="expendable" shrink>Author</vn-th>
<vn-th field="changedModel" class="expendable">Model</vn-th>
<vn-th field="action" class="expendable" shrink>Action</vn-th>
<vn-th field="changedModelValue" class="expendable">Name</vn-th>
<vn-th expand>Before</vn-th>
<vn-th expand>After</vn-th>
<vn-th field="userFk" shrink>User</vn-th>
<vn-th field="changedModel" ng-if="$ctrl.showModelName" shrink>Model</vn-th>
<vn-th field="action" shrink>Action</vn-th>
<vn-th field="changedModelValue" ng-if="$ctrl.showModelName">Name</vn-th>
<vn-th expand>Changes</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="log in $ctrl.logs">
<vn-td shrink-datetime>
{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}
<div class="changes">
<div>
<span translate class="label">Changed by</span><span class="label">: </span>
<span ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name || 'System' | translate}}
</span>
</div>
<div>
<span translate class="label">Model</span><span class="label">: </span>
<span translate class="value">{{::log.changedModel | dashIfEmpty}}</span>
</div>
<div>
<span translate class="label">Action</span><span class="label">: </span>
<span translate class="value">{{::$ctrl.actionsText[log.action] | dashIfEmpty}}</span>
</div>
<div>
<span translate class="label">Name</span><span class="label">: </span>
<span translate class="value">{{::log.changedModelValue | dashIfEmpty}}</span>
</div>
</div>
</vn-td>
<vn-td class="expendable">
<vn-td>
<span ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name || 'System' | translate}}
</span>
</vn-td>
<vn-td class="expendable">
<vn-td ng-if="$ctrl.showModelName">
{{::log.changedModel}}
</vn-td>
<vn-td translate class="expendable">
<vn-td shrink translate>
{{::$ctrl.actionsText[log.action]}}
</vn-td>
<vn-td class="expendable" expand>
<vn-td ng-if="$ctrl.showModelName">
{{::log.changedModelValue}}
</vn-td>
<vn-td expand class="before">
<vn-one ng-repeat="old in log.oldProperties">
<div>
<vn-label-value
no-ellipsize
label="{{::old.key}}"
value="{{::old.value}}">
</vn-label-value>
<vn-td expand>
<table class="attributes">
<thead>
<tr>
<th translate class="field">Field</th>
<th translate>Before</th>
<th translate>After</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="prop in ::log.props">
<td class="field">{{prop.name}}</td>
<td class="before">{{prop.old}}</td>
<td class="after">{{prop.new}}</td>
</tr>
</tbody>
</table>
<div ng-if="log.description != null">
{{::log.description}}
</div>
</vn-one>
</vn-td>
<vn-td expand class="after">
<vn-one ng-repeat="new in log.newProperties" ng-if="!log.description" id="newInstance">
<div>
<vn-label-value
no-ellipsize
label="{{::new.key}}"
value="{{::new.value}}">
</vn-label-value>
</div>
</vn-one>
<vn-one ng-if="!log.newProperties" id="description">
<div>
<span no-ellipsize>{{::log.description}}</span>
</div>
</vn-one>
</vn-td>
</vn-tr>
</vn-tbody>

View File

@ -2,15 +2,17 @@ import ngModule from '../../module';
import Section from '../section';
import './style.scss';
const validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/;
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.actionsText = {
'insert': 'Creates',
'update': 'Updates',
'delete': 'Deletes',
'select': 'Views'
}; ``;
insert: 'Creates',
update: 'Updates',
delete: 'Deletes',
select: 'Views'
};
this.filter = {
include: [{
relation: 'user',
@ -33,32 +35,57 @@ export default class Controller extends Section {
set logs(value) {
this._logs = value;
if (!value) return;
if (!this.logs) return;
const empty = {};
const validations = window.validations;
value.forEach(log => {
const locale = validations[log.changedModel] && validations[log.changedModel].locale ? validations[log.changedModel].locale : {};
for (const log of value) {
const oldValues = log.oldInstance || empty;
const newValues = log.newInstance || empty;
const locale = validations[log.changedModel]?.locale || empty;
log.oldProperties = this.getInstance(log.oldInstance, locale);
log.newProperties = this.getInstance(log.newInstance, locale);
let props = Object.keys(oldValues).concat(Object.keys(newValues));
props = [...new Set(props)];
log.props = [];
for (const prop of props) {
log.props.push({
name: locale[prop] || prop,
old: this.formatValue(oldValues[prop]),
new: this.formatValue(newValues[prop])
});
}
getInstance(instance, locale) {
const properties = [];
let validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/;
if (typeof instance == 'object' && instance != null) {
Object.keys(instance).forEach(property => {
if (validDate.test(instance[property]))
instance[property] = new Date(instance[property]).toLocaleString('es-ES');
const key = locale[property] || property;
properties.push({key, value: instance[property]});
});
return properties;
}
return null;
}
get showModelName() {
return !(this.changedModel && this.changedModelId);
}
formatValue(value) {
let type = typeof value;
if (type === 'string' && validDate.test(value)) {
value = new Date(value);
type = typeof value;
}
switch (type) {
case 'boolean':
return value ? '✓' : '✗';
case 'object':
if (value instanceof Date) {
const hasZeroTime =
value.getHours() === 0 &&
value.getMinutes() === 0 &&
value.getSeconds() === 0;
const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss';
return this.$filter('date')(value, format);
}
else
return value;
default:
return value;
}
}
showWorkerDescriptor(event, workerId) {
@ -73,6 +100,8 @@ ngModule.vnComponent('vnLog', {
bindings: {
model: '<',
originId: '<',
changedModel: '<?',
changedModelId: '<?',
url: '@'
}
});

View File

@ -12,3 +12,4 @@ Deletes: Elimina
Views: Visualiza
System: Sistema
note: nota
Changes: Cambios

View File

@ -23,6 +23,28 @@ vn-log {
display: block;
}
}
.attributes {
width: 100%;
tr {
height: 10px;
& > td {
padding: 2px;
}
& > td.field,
& > th.field {
width: 20%;
color: gray;
}
& > td.before,
& > th.before,
& > td.after,
& > th.after {
width: 40%;
}
}
}
}
.ellipsis {
white-space: nowrap;

View File

@ -1,6 +1,27 @@
<div class="box">
<img src="./logo.svg"/>
<form name="form">
<ui-view></ui-view>
</form>
<vn-textfield
label="User"
ng-model="$ctrl.user"
vn-id="userField"
vn-focus>
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.password"
type="password">
</vn-textfield>
<vn-check
label="Do not close session"
ng-model="$ctrl.remember"
name="remember">
</vn-check>
<div class="footer">
<vn-submit label="Enter" ng-click="$ctrl.submit()"></vn-submit>
<div class="spinner-wrapper">
<vn-spinner enable="$ctrl.loading"></vn-spinner>
</div>
<div class="vn-pt-lg">
<a ui-sref="recover-password" translate>
I do not remember my password
</a>
</div>
</div>

View File

@ -1,16 +1,43 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
export default class OutLayout extends Component {
constructor($element, $scope) {
super($element, $scope);
}
}
OutLayout.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnOutLayout', {
template: require('./index.html'),
controller: OutLayout
/**
* A simple login form.
*/
export default class Controller {
constructor($, $element, vnAuth) {
Object.assign(this, {
$,
$element,
vnAuth,
user: localStorage.getItem('lastUser'),
remember: true
});
}
submit() {
this.loading = true;
this.vnAuth.login(this.user, this.password, this.remember)
.then(() => {
localStorage.setItem('lastUser', this.user);
this.loading = false;
})
.catch(err => {
this.loading = false;
this.password = '';
this.focusUser();
throw err;
});
}
focusUser() {
this.$.userField.select();
this.$.userField.focus();
}
}
Controller.$inject = ['$scope', '$element', 'vnAuth'];
ngModule.vnComponent('vnLogin', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,16 +1,5 @@
User: Usuario
Password: Contraseña
Email: Correo electrónico
Do not close session: No cerrar sesión
Enter: Entrar
I do not remember my password: No recuerdo mi contraseña
Recover password: Recuperar contraseña
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
Notification sent!: ¡Notificación enviada!
Reset password: Restrablecer contraseña
New password: Nueva contraseña
Repeat password: Repetir contraseña
Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud,
{{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}}
dígitos y {{nPunct}} símbolos (Ej: $%&.)

View File

@ -1,32 +0,0 @@
<div class="box">
<img src="./logo.svg"/>
<form name="form" ng-submit="$ctrl.submit()">
<vn-textfield
label="User"
ng-model="$ctrl.user"
vn-id="userField"
vn-focus>
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.password"
type="password">
</vn-textfield>
<vn-check
label="Do not close session"
ng-model="$ctrl.remember"
name="remember">
</vn-check>
<div class="footer">
<vn-submit label="Enter" ng-click="$ctrl.submit()"></vn-submit>
<div class="spinner-wrapper">
<vn-spinner enable="$ctrl.loading"></vn-spinner>
</div>
<!--<div class="vn-pt-lg">
<a ui-sref="recoverPassword" translate>
I do not remember my password
</a>
</div>-->
</div>
</form>
</div>

View File

@ -1,43 +0,0 @@
import ngModule from '../../module';
import './style.scss';
/**
* A simple login form.
*/
export default class Controller {
constructor($, $element, vnAuth) {
Object.assign(this, {
$,
$element,
vnAuth,
user: localStorage.getItem('lastUser'),
remember: true
});
}
submit() {
this.loading = true;
this.vnAuth.login(this.user, this.password, this.remember)
.then(() => {
localStorage.setItem('lastUser', this.user);
this.loading = false;
})
.catch(err => {
this.loading = false;
this.password = '';
this.focusUser();
throw err;
});
}
focusUser() {
this.$.userField.select();
this.$.userField.focus();
}
}
Controller.$inject = ['$scope', '$element', 'vnAuth'];
ngModule.vnComponent('vnLogin', {
template: require('./login.html'),
controller: Controller
});

View File

@ -1,8 +1,6 @@
@import "variables";
vn-login,
vn-reset-password,
vn-recover-password{
vn-login{
.footer {
margin-top: 32px;
text-align: center;
@ -24,69 +22,3 @@ vn-recover-password{
}
}
}
vn-login{
position: absolute;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
color: $color-font;
font-size: 1.1rem;
font-weight: normal;
background-color: $color-bg-dark;
display: flex;
justify-content: center;
align-items: center;
overflow: auto;
& > .box {
box-sizing: border-box;
position: absolute;
max-width: 304px;
min-width: 240px;
padding: 48px;
background-color: $color-bg-panel;
box-shadow: 0 0 16px 0 rgba(0, 0, 0, .6);
border-radius: 8px;
& > img {
width: 100%;
padding-bottom: 16px;
}
& > form {
& > .vn-textfield {
width: 100%;
}
& > .vn-check {
display: block;
.md-label {
white-space: inherit;
}
}
}
h5{
color: $color-primary;
}
.text-secondary{
text-align: center;
padding-bottom: 16px;
}
}
@media screen and (max-width: 600px) {
background-color: $color-bg-panel;
& > .box {
padding: 16px;
box-shadow: none;
}
}
a{
color: $color-primary;
}
}

View File

@ -0,0 +1,6 @@
<div class="box">
<img src="./logo.svg"/>
<form name="form">
<ui-view></ui-view>
</form>
</div>

View File

@ -0,0 +1,16 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
export default class OutLayout extends Component {
constructor($element, $scope) {
super($element, $scope);
}
}
OutLayout.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnOutLayout', {
template: require('./index.html'),
controller: OutLayout
});

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,67 @@
@import "variables";
vn-out-layout{
position: absolute;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
color: $color-font;
font-size: 1.1rem;
font-weight: normal;
background-color: $color-bg-dark;
display: flex;
justify-content: center;
align-items: center;
overflow: auto;
& > .box {
box-sizing: border-box;
position: absolute;
max-width: 304px;
min-width: 240px;
padding: 48px;
background-color: $color-bg-panel;
box-shadow: 0 0 16px 0 rgba(0, 0, 0, .6);
border-radius: 8px;
& > img {
width: 100%;
padding-bottom: 16px;
}
& > form {
& > .vn-textfield {
width: 100%;
}
& > .vn-check {
display: block;
.md-label {
white-space: inherit;
}
}
}
h5{
color: $color-primary;
}
.text-secondary{
text-align: center;
padding-bottom: 16px;
}
}
@media screen and (max-width: 600px) {
background-color: $color-bg-panel;
& > .box {
padding: 16px;
box-shadow: none;
}
}
a{
color: $color-primary;
}
}

View File

@ -32,6 +32,6 @@ export default class Controller {
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state'];
ngModule.vnComponent('vnRecoverPassword', {
template: require('./recover-password.html'),
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,3 @@
Recover password: Recuperar contraseña
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
Notification sent!: ¡Notificación enviada!

View File

@ -0,0 +1,24 @@
@import "variables";
vn-recover-password{
.footer {
margin-top: 32px;
text-align: center;
position: relative;
& > .vn-submit {
display: block;
& > input {
display: block;
width: 100%;
}
}
& > .spinner-wrapper {
position: absolute;
width: 0;
top: 3px;
right: -8px;
overflow: visible;
}
}
}

View File

@ -43,6 +43,6 @@ export default class Controller {
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state', '$location'];
ngModule.vnComponent('vnResetPassword', {
template: require('./reset-password.html'),
template: require('./index.html'),
controller: Controller
});

View File

@ -1,7 +1,3 @@
User: User
Password: Password
Do not close session: Do not close session
Enter: Enter
Password requirements: >
The password must have at least {{ length }} length characters,
{{nAlpha}} alphabetic characters, {{nUpper}} capital letters, {{nDigits}}

View File

@ -0,0 +1,8 @@
Reset password: Restrablecer contraseña
New password: Nueva contraseña
Repeat password: Repetir contraseñaç
Password changed!: ¡Contraseña cambiada!
Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud,
{{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}}
dígitos y {{nPunct}} símbolos (Ej: $%&.)

View File

@ -0,0 +1,24 @@
@import "variables";
vn-reset-password{
.footer {
margin-top: 32px;
text-align: center;
position: relative;
& > .vn-submit {
display: block;
& > input {
display: block;
width: 100%;
}
}
& > .spinner-wrapper {
position: absolute;
width: 0;
top: 3px;
right: -8px;
overflow: visible;
}
}
}

View File

@ -3,27 +3,38 @@ import getMainRoute from 'core/lib/get-main-route';
config.$inject = ['$stateProvider', '$urlRouterProvider'];
function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$urlRouterProvider
.otherwise('/');
$stateProvider
.state('layout', {
abstract: true,
template: '<vn-layout></vn-layout>',
})
.state('outLayout', {
abstract: true,
template: '<vn-out-layout></vn-out-layout>',
})
.state('login', {
parent: 'outLayout',
url: '/login?continue',
description: 'Login',
views: {
'login': {template: '<vn-login></vn-login>'},
}
template: '<vn-login></vn-login>'
})
.state('recoverPassword', {
.state('recover-password', {
parent: 'outLayout',
url: '/recover-password',
description: 'Recover-password',
template: '<vn-recover-password>asd</vn-recover-password>'
description: 'Recover password',
template: '<vn-recover-password></vn-recover-password>'
})
.state('resetPassword', {
.state('reset-password', {
parent: 'outLayout',
url: '/reset-password',
description: 'Reset-password',
description: 'Reset password',
template: '<vn-reset-password></vn-reset-password>'
})
.state('home', {
parent: 'layout',
url: '/',
description: 'Home',
template: '<vn-home></vn-home>'
@ -54,6 +65,10 @@ function config($stateProvider, $urlRouterProvider) {
};
if (route.abstract)
configRoute.abstract = true;
if (!route.state.includes('.'))
configRoute.parent = 'layout';
if (route.routeParams)
configRoute.params = route.routeParams;

View File

@ -73,6 +73,12 @@ export default class Controller extends Section {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
}
ngModule.vnComponent('vnEntryBuyIndex', {

View File

@ -32,9 +32,6 @@
"Sale": {
"dataSource": "vn"
},
"SaleChecked": {
"dataSource": "vn"
},
"SaleCloned": {
"dataSource": "vn"
},

View File

@ -1,24 +0,0 @@
{
"name": "SaleChecked",
"base": "VnModel",
"options": {
"mysql": {
"table": "saleChecked"
}
},
"properties": {
"isChecked": {
"type": "number"
},
"saleFk": {
"id": true
}
},
"relations": {
"sale": {
"type": "belongsTo",
"model": "Sale",
"foreignKey": "saleFk"
}
}
}

View File

@ -56,11 +56,6 @@
"foreignKey": "ticketFk",
"required": true
},
"isChecked": {
"type": "hasOne",
"model": "SaleChecked",
"foreignKey": "saleFk"
},
"components": {
"type": "hasMany",
"model": "SaleComponent",

View File

@ -20,7 +20,6 @@ import './package/index';
import './sale';
import './tracking/index';
import './tracking/edit';
import './sale-checked';
import './services';
import './component';
import './sale-tracking';

View File

@ -23,7 +23,6 @@
{"state": "ticket.card.expedition", "icon": "icon-package"},
{"state": "ticket.card.service", "icon": "icon-services"},
{"state": "ticket.card.package", "icon": "icon-bucket"},
{"state": "ticket.card.saleChecked", "icon": "assignment"},
{"state": "ticket.card.components", "icon": "icon-components"},
{"state": "ticket.card.saleTracking", "icon": "assignment"},
{"state": "ticket.card.dms.index", "icon": "cloud_download"},
@ -159,15 +158,6 @@
},
"acl": ["production", "administrative", "salesPerson"]
},
{
"url" : "/sale-checked",
"state": "ticket.card.saleChecked",
"component": "vn-ticket-sale-checked",
"description": "Sale checked",
"params": {
"ticket": "$ctrl.ticket"
}
},
{
"url" : "/components",
"state": "ticket.card.components",

View File

@ -1,60 +0,0 @@
<vn-crud-model
vn-id="model"
url="sales"
filter="::$ctrl.filter"
link="{ticketFk: $ctrl.$params.id}"
limit="20"
data="sales"
order="concept ASC"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model">
<vn-card class="vn-w-lg">
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="isChecked" center>Is checked</vn-th>
<vn-th field="itemFk" number>Item</vn-th>
<vn-th field="concept">Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in sales">
<vn-td center shrink>
<vn-check
vn-one ng-model="sale.isChecked.isChecked"
disabled="true">
</vn-check>
</vn-td>
<vn-td number>
<span
ng-click="$ctrl.showItemDescriptor($event, sale)"
class="link">
{{::sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td vn-fetched-tags>
<div>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one>
<vn-one ng-if="::sale.item.subName">
<h3 title="{{::sale.item.subName}}">{{::sale.item.subName}}</h3>
</vn-one>
</div>
<vn-fetched-tags
max-length="6"
item="::sale.item"
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk"
ticket-fk="$ctrl.ticket.id">
</vn-item-descriptor-popover>

View File

@ -1,42 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
include: [
{
relation: 'item'
}, {
relation: 'isChecked',
scope: {
fields: ['isChecked']
}
}
]
};
}
showItemDescriptor(event, sale) {
this.quicklinks = {
btnThree: {
icon: 'icon-transaction',
state: `item.card.diary({
id: ${sale.itemFk},
warehouseFk: ${this.ticket.warehouseFk},
lineFk: ${sale.id}
})`,
tooltip: 'Item diary'
}
};
this.$.itemDescriptor.show(event.target, sale.itemFk);
}
}
ngModule.vnComponent('vnTicketSaleChecked', {
template: require('./index.html'),
controller: Controller,
bindings: {
ticket: '<'
}
});

View File

@ -10,6 +10,7 @@ describe('workerTimeControl sendMail()', () => {
const ctx = {req: activeCtx, args: {}};
it('should fill time control of a worker without records in Journey and with rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
@ -34,6 +35,7 @@ describe('workerTimeControl sendMail()', () => {
});
it('should fill time control of a worker without records in Journey and without rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const workdayOf20Hours = 3;
const tx = await models.WorkerTimeControl.beginTransaction({});
@ -61,6 +63,7 @@ describe('workerTimeControl sendMail()', () => {
});
it('should fill time control of a worker with records in Journey and with rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
@ -92,6 +95,7 @@ describe('workerTimeControl sendMail()', () => {
});
it('should fill time control of a worker with records in Journey and without rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({});
try {

View File

@ -201,6 +201,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
describe('WorkerTimeControl_clockIn calls', () => {
it('should fail to add a time entry if the target user has an absence that day', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
const date = new Date();
@ -247,6 +248,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
describe('direction errors', () => {
it('should throw an error when trying "in" direction twice', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
@ -276,6 +278,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
});
it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
@ -309,6 +312,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
});
it('Should throw an error when trying "out" before closing a "middle" couple', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
@ -342,6 +346,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
});
it('should throw an error when trying "middle" after "out"', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
@ -375,6 +380,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
});
it('should throw an error when trying "out" direction twice', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
@ -446,6 +452,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
});
it('should not fail as the 12h rest is fulfilled', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
@ -482,6 +489,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
describe('for 3500kg drivers with enforced 9h rest', () => {
it('should throw an error when the 9h enforced rest is not fulfilled', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = jessicaJonesId;
@ -516,6 +524,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
});
it('should not fail when the 9h enforced rest is fulfilled', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = jessicaJonesId;
@ -552,6 +561,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
describe('for 36h weekly rest', () => {
it('should throw an error when the 36h weekly rest is not fulfilled', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
@ -586,6 +596,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
});
it('should throw an error when the 36h weekly rest is not fulfilled again', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;
@ -619,6 +630,7 @@ describe('workerTimeControl add/delete timeEntry()', () => {
describe('for 72h weekly rest', () => {
it('should throw when the 72h weekly rest is not fulfilled yet', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
activeCtx.accessToken.userId = salesBossId;
const workerId = hankPymId;