Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3963-ticket-advance
This commit is contained in:
commit
8fec0670ea
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
|||
let message = $t(`There's a new urgent ticket:`);
|
||||
const ostUri = 'https://cau.verdnatura.es/scp/tickets.php?id=';
|
||||
tickets.forEach(ticket => {
|
||||
message += `\r\n[ID: *${ticket.number}* - ${ticket.subject} (@${ticket.username})](${ostUri + ticket.id})`;
|
||||
message += `\r\n[ID: *${ticket.number}* - ${ticket.subject} @${ticket.username}](${ostUri + ticket.id})`;
|
||||
});
|
||||
|
||||
const department = await models.Department.findOne({
|
||||
|
@ -42,7 +42,5 @@ module.exports = Self => {
|
|||
|
||||
if (channelName)
|
||||
return Self.send(ctx, `#${channelName}`, `@all ➔ ${message}`);
|
||||
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,4 +4,21 @@ module.exports = Self => {
|
|||
require('../methods/chat/sendCheckingPresence')(Self);
|
||||
require('../methods/chat/notifyIssues')(Self);
|
||||
require('../methods/chat/sendQueued')(Self);
|
||||
|
||||
Self.observe('before save', async function(ctx) {
|
||||
if (!ctx.isNewInstance) return;
|
||||
|
||||
let {message} = ctx.instance;
|
||||
if (!message) return;
|
||||
|
||||
const parts = message.match(/(?<=\[).*(?=])/g);
|
||||
const replacedParts = parts.map(part => {
|
||||
return part.replace(/[*()]/g, '');
|
||||
});
|
||||
|
||||
for (const [index, part] of parts.entries())
|
||||
message = message.replace(part, replacedParts[index]);
|
||||
|
||||
ctx.instance.message = message;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
|
||||
VALUES ('NotificationQueue','*','*','ALLOW','ROLE','employee');
|
|
@ -0,0 +1,8 @@
|
|||
ALTER TABLE
|
||||
`vn`.`client`
|
||||
ADD
|
||||
COLUMN `hasElectronicInvoice` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Registro de facturas mediante FACe'
|
||||
AFTER
|
||||
`hasInvoiceSimplified`;
|
||||
|
||||
-- sería más correcto hasElectronicInvoice pero ya existe un campo hasInvoiceSimplified
|
|
@ -0,0 +1 @@
|
|||
insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoiceElectronic', 'A electronic invoice has been generated');
|
|
@ -0,0 +1,6 @@
|
|||
UPDATE
|
||||
`vn`.`client`
|
||||
SET
|
||||
hasElectronicInvoice = TRUE
|
||||
WHERE
|
||||
businessTypeFk = 'officialOrganism';
|
|
@ -0,0 +1 @@
|
|||
Delete this file
|
|
@ -2734,3 +2734,7 @@ UPDATE `account`.`user`
|
|||
INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`)
|
||||
VALUES
|
||||
(0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all');
|
||||
|
||||
INSERT INTO `vn`.`ticketLog` (`id`, `originFk`, `userFk`, `action`, `changedModel`, `oldInstance`, `newInstance`, `changedModelId`)
|
||||
VALUES
|
||||
(1, 1, 9, 'insert', 'Ticket', '{}', '{"clientFk":1, "nickname": "Bat cave"}', 1);
|
|
@ -19,6 +19,7 @@
|
|||
-- Current Database: `account`
|
||||
--
|
||||
|
||||
|
||||
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `account` /*!40100 DEFAULT CHARACTER SET utf8mb3 */;
|
||||
|
||||
USE `account`;
|
||||
|
|
|
@ -26,6 +26,7 @@ export default class Searchbar extends Component {
|
|||
this.autoState = true;
|
||||
this.separateIndex = true;
|
||||
this.entityState = 'card.summary';
|
||||
this.isIndex = false;
|
||||
|
||||
this.deregisterCallback = this.$transitions.onSuccess(
|
||||
{}, transition => this.onStateChange(transition));
|
||||
|
@ -102,6 +103,9 @@ export default class Searchbar extends Component {
|
|||
filter = {};
|
||||
}
|
||||
|
||||
let stateParts = this.$state.current.name.split('.');
|
||||
this.isIndex = stateParts[1] == 'index';
|
||||
|
||||
this.doSearch(filter, 'state');
|
||||
}
|
||||
|
||||
|
@ -198,7 +202,7 @@ export default class Searchbar extends Component {
|
|||
}
|
||||
|
||||
doSearch(filter, source) {
|
||||
if (filter === this.filter && source != 'state') return;
|
||||
if (filter === this.filter && !this.isIndex) return;
|
||||
let promise = this.onSearch({$params: filter});
|
||||
promise = promise || this.$q.resolve();
|
||||
promise.then(data => this.onFilter(filter, source, data));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
|
@ -189,10 +189,14 @@
|
|||
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
|
||||
},
|
||||
"angular-animate": {
|
||||
"version": "1.8.2"
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.2.tgz",
|
||||
"integrity": "sha512-Jbr9+grNMs9Kj57xuBU3Ju3NOPAjS1+g2UAwwDv7su1lt0/PLDy+9zEwDiu8C8xJceoTbmBNKiWGPJGBdCQLlA=="
|
||||
},
|
||||
"angular-moment": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
|
||||
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
|
||||
"requires": {
|
||||
"moment": ">=2.8.0 <3.0.0"
|
||||
}
|
||||
|
@ -215,18 +219,26 @@
|
|||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"croppie": {
|
||||
"version": "2.6.5"
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
|
||||
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1"
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
|
@ -234,6 +246,8 @@
|
|||
},
|
||||
"mg-crud": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
|
||||
"integrity": "sha512-mAR6t0aQHKnT0QHKHpLOi0kNPZfO36iMpIoiLjFHxuio6mIJyuveBJ4VNlNXJRxLh32/FLADEb41/sYo7QUKFw==",
|
||||
"requires": {
|
||||
"angular": "^1.6.1"
|
||||
}
|
||||
|
@ -244,19 +258,27 @@
|
|||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||
},
|
||||
"oclazyload": {
|
||||
"version": "0.6.3"
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz",
|
||||
"integrity": "sha512-HpOSYUgjtt6sTB/C6+FWsExR+9HCnXKsUA96RWkDXfv11C8Cc9X2DlR0WIZwFIiG6FQU0pwB5dhoYyut8bFAOQ=="
|
||||
},
|
||||
"require-yaml": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
|
||||
"integrity": "sha512-M6eVEgLPRbeOhgSCnOTtdrOOEQzbXRchg24Xa13c39dMuraFKdI9emUo97Rih0YEFzSICmSKg8w4RQp+rd9pOQ==",
|
||||
"requires": {
|
||||
"js-yaml": ""
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
"version": "2.0.1"
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
}
|
||||
|
@ -264,10 +286,14 @@
|
|||
}
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3"
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
|
||||
},
|
||||
"validator": {
|
||||
"version": "6.3.0"
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
|
||||
"integrity": "sha512-BylxTwhqwjQI5MDJF7amCy/L0ejJO+74DvCsLV52Lq3+3bhVcVMKqNqOiNcQJm2G48u9EAcw4xFERAmFbwXM9Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,16 +33,18 @@ export default class Controller extends Section {
|
|||
|
||||
set logs(value) {
|
||||
this._logs = value;
|
||||
if (!value) return;
|
||||
|
||||
if (this.logs) {
|
||||
this.logs.forEach(log => {
|
||||
log.oldProperties = this.getInstance(log.oldInstance);
|
||||
log.newProperties = this.getInstance(log.newInstance);
|
||||
});
|
||||
}
|
||||
const validations = window.validations;
|
||||
value.forEach(log => {
|
||||
const locale = validations[log.changedModel].locale ? validations[log.changedModel].locale : {};
|
||||
|
||||
log.oldProperties = this.getInstance(log.oldInstance, locale);
|
||||
log.newProperties = this.getInstance(log.newInstance, locale);
|
||||
});
|
||||
}
|
||||
|
||||
getInstance(instance) {
|
||||
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)?$/;
|
||||
|
||||
|
@ -51,7 +53,8 @@ export default class Controller extends Section {
|
|||
if (validDate.test(instance[property]))
|
||||
instance[property] = new Date(instance[property]).toLocaleString('es-ES');
|
||||
|
||||
properties.push({key: property, value: instance[property]});
|
||||
const key = locale[property] || property;
|
||||
properties.push({key, value: instance[property]});
|
||||
});
|
||||
return properties;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('modelInfo', {
|
||||
|
@ -19,6 +21,42 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
const modelsLocale = new Map();
|
||||
const modulesDir = path.resolve(`${__dirname}/../../../../modules`);
|
||||
const modules = fs.readdirSync(modulesDir);
|
||||
|
||||
for (const mod of modules) {
|
||||
const modelsDir = path.join(modulesDir, mod, `back/locale`);
|
||||
if (!fs.existsSync(modelsDir)) continue;
|
||||
const models = fs.readdirSync(modelsDir);
|
||||
|
||||
for (const model of models) {
|
||||
const localeDir = path.join(modelsDir, model);
|
||||
const localeFiles = fs.readdirSync(localeDir);
|
||||
|
||||
let modelName = model.charAt(0).toUpperCase() + model.substring(1);
|
||||
modelName = modelName.replace(/-\w/g, match => {
|
||||
return match.charAt(1).toUpperCase();
|
||||
});
|
||||
|
||||
const modelLocale = new Map();
|
||||
modelsLocale.set(modelName, modelLocale);
|
||||
|
||||
for (const localeFile of localeFiles) {
|
||||
const localePath = path.join(localeDir, localeFile);
|
||||
|
||||
const match = localeFile.match(/^([a-z]+)\.yml$/);
|
||||
if (!match) {
|
||||
console.warn(`Skipping wrong model locale file: ${localeFile}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const translations = require(localePath);
|
||||
modelLocale.set(match[1], translations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self.modelInfo = async function(ctx) {
|
||||
let json = {};
|
||||
let models = Self.app.models;
|
||||
|
@ -49,9 +87,14 @@ module.exports = Self => {
|
|||
jsonValidations[fieldName] = jsonField;
|
||||
}
|
||||
|
||||
const modelLocale = modelsLocale.get(modelName);
|
||||
const lang = ctx.req.getLocale();
|
||||
const locale = modelLocale && modelLocale.get(lang);
|
||||
|
||||
json[modelName] = {
|
||||
properties: model.definition.rawProperties,
|
||||
validations: jsonValidations
|
||||
validations: jsonValidations,
|
||||
locale
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -99,6 +99,10 @@ module.exports = Self => {
|
|||
{
|
||||
arg: 'hasIncoterms',
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
arg: 'hasElectronicInvoice',
|
||||
type: 'boolean'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
|
@ -122,19 +126,15 @@ module.exports = Self => {
|
|||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions);
|
||||
const client = await models.Client.findById(clientId, null, myOptions);
|
||||
|
||||
if (!isSalesAssistant && client.isTaxDataChecked)
|
||||
throw new UserError(`Not enough privileges to edit a client with verified data`);
|
||||
|
||||
// Sage data validation
|
||||
const taxDataChecked = args.isTaxDataChecked;
|
||||
const sageTaxChecked = client.sageTaxTypeFk || args.sageTaxTypeFk;
|
||||
|
@ -143,7 +143,6 @@ module.exports = Self => {
|
|||
|
||||
if (taxDataChecked && !hasSageData)
|
||||
throw new UserError(`You need to fill sage information before you check verified data`);
|
||||
|
||||
if (args.despiteOfClient) {
|
||||
const logRecord = {
|
||||
originFk: clientId,
|
||||
|
@ -158,7 +157,6 @@ module.exports = Self => {
|
|||
|
||||
await models.ClientLog.create(logRecord, myOptions);
|
||||
}
|
||||
|
||||
// Remove unwanted properties
|
||||
delete args.ctx;
|
||||
delete args.id;
|
||||
|
|
|
@ -142,7 +142,11 @@
|
|||
},
|
||||
"salesPersonFk": {
|
||||
"type": "number"
|
||||
},
|
||||
"hasElectronicInvoice": {
|
||||
"type": "boolean"
|
||||
}
|
||||
|
||||
},
|
||||
"relations": {
|
||||
"account": {
|
||||
|
|
|
@ -1,112 +1,52 @@
|
|||
<mg-ajax path="Clients/{{patch.params.id}}/updateFiscalData" options="vnPatch"></mg-ajax>
|
||||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
data="$ctrl.client"
|
||||
id-field="id"
|
||||
form="form"
|
||||
save="patch">
|
||||
<vn-watcher vn-id="watcher" data="$ctrl.client" id-field="id" form="form" save="patch">
|
||||
</vn-watcher>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="Provinces/location"
|
||||
data="provincesLocation"
|
||||
order="name">
|
||||
<vn-crud-model auto-load="true" url="Provinces/location" data="provincesLocation" order="name">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="Countries"
|
||||
data="countries"
|
||||
order="country">
|
||||
<vn-crud-model auto-load="true" url="Countries" data="countries" order="country">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="SageTaxTypes"
|
||||
data="sageTaxTypes"
|
||||
order="vat">
|
||||
<vn-crud-model auto-load="true" url="SageTaxTypes" data="sageTaxTypes" order="vat">
|
||||
</vn-crud-model>
|
||||
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-two
|
||||
vn-focus
|
||||
label="Social name"
|
||||
ng-model="$ctrl.client.socialName"
|
||||
rule
|
||||
info="Only letters, numbers and spaces can be used"
|
||||
required="true">
|
||||
<vn-textfield vn-two vn-focus label="Social name" ng-model="$ctrl.client.socialName" rule
|
||||
info="Only letters, numbers and spaces can be used" required="true">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Tax number"
|
||||
ng-model="$ctrl.client.fi"
|
||||
rule>
|
||||
<vn-textfield vn-one label="Tax number" ng-model="$ctrl.client.fi" rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-two
|
||||
label="Street"
|
||||
ng-model="$ctrl.client.street"
|
||||
rule>
|
||||
<vn-textfield vn-two label="Street" ng-model="$ctrl.client.street" rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
ng-model="$ctrl.client.sageTaxTypeFk"
|
||||
data="sageTaxTypes"
|
||||
show-field="vat"
|
||||
value-field="id"
|
||||
label="Sage tax type"
|
||||
vn-acl="salesAssistant"
|
||||
rule>
|
||||
<vn-autocomplete vn-one ng-model="$ctrl.client.sageTaxTypeFk" data="sageTaxTypes" show-field="vat"
|
||||
value-field="id" label="Sage tax type" vn-acl="salesAssistant" rule>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete vn-one
|
||||
ng-model="$ctrl.client.sageTransactionTypeFk"
|
||||
url="SageTransactionTypes"
|
||||
show-field="transaction"
|
||||
value-field="id"
|
||||
label="Sage transaction type"
|
||||
<vn-autocomplete vn-one ng-model="$ctrl.client.sageTransactionTypeFk" url="SageTransactionTypes"
|
||||
show-field="transaction" value-field="id" label="Sage transaction type"
|
||||
search-function="{or: [{id: $search}, {transaction: {like: '%'+ $search +'%'}}]}"
|
||||
vn-acl="salesAssistant"
|
||||
order="transaction"
|
||||
rule>
|
||||
vn-acl="salesAssistant" order="transaction" rule>
|
||||
<tpl-item>{{id}}: {{transaction}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-datalist vn-one
|
||||
label="Postcode"
|
||||
ng-model="$ctrl.client.postcode"
|
||||
selection="$ctrl.postcode"
|
||||
url="Postcodes/location"
|
||||
fields="['code','townFk']"
|
||||
order="code, townFk"
|
||||
value-field="code"
|
||||
show-field="code"
|
||||
rule>
|
||||
<vn-datalist vn-one label="Postcode" ng-model="$ctrl.client.postcode" selection="$ctrl.postcode"
|
||||
url="Postcodes/location" fields="['code','townFk']" order="code, townFk" value-field="code"
|
||||
show-field="code" rule>
|
||||
<tpl-item>
|
||||
{{code}} - {{town.name}} ({{town.province.name}},
|
||||
{{code}} - {{town.name}} ({{town.province.name}},
|
||||
{{town.province.country.country}})
|
||||
</tpl-item>
|
||||
<append>
|
||||
<vn-icon-button
|
||||
icon="add_circle"
|
||||
vn-tooltip="New postcode"
|
||||
ng-click="postcode.open()"
|
||||
vn-acl="deliveryBoss"
|
||||
vn-acl-action="remove">
|
||||
<vn-icon-button icon="add_circle" vn-tooltip="New postcode" ng-click="postcode.open()"
|
||||
vn-acl="deliveryBoss" vn-acl-action="remove">
|
||||
</vn-icon-button>
|
||||
</append>
|
||||
</vn-datalist>
|
||||
<vn-datalist vn-id="town" vn-one
|
||||
label="City"
|
||||
ng-model="$ctrl.client.city"
|
||||
selection="$ctrl.town"
|
||||
url="Towns/location"
|
||||
fields="['id', 'name', 'provinceFk']"
|
||||
show-field="name"
|
||||
value-field="name">
|
||||
<vn-datalist vn-id="town" vn-one label="City" ng-model="$ctrl.client.city" selection="$ctrl.town"
|
||||
url="Towns/location" fields="['id', 'name', 'provinceFk']" show-field="name" value-field="name">
|
||||
<tpl-item>
|
||||
{{name}}, {{province.name}}
|
||||
({{province.country.country}})
|
||||
|
@ -114,112 +54,64 @@
|
|||
</vn-datalist>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-id="province" vn-one
|
||||
label="Province"
|
||||
ng-model="$ctrl.client.provinceFk"
|
||||
selection="$ctrl.province"
|
||||
data="provincesLocation"
|
||||
fields="['id', 'name', 'countryFk']"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
rule>
|
||||
<vn-autocomplete vn-id="province" vn-one label="Province" ng-model="$ctrl.client.provinceFk"
|
||||
selection="$ctrl.province" data="provincesLocation" fields="['id', 'name', 'countryFk']"
|
||||
show-field="name" value-field="id" rule>
|
||||
<tpl-item>{{name}} ({{country.country}})</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete vn-id="country" vn-one
|
||||
ng-model="$ctrl.client.countryFk"
|
||||
data="countries"
|
||||
show-field="country"
|
||||
value-field="id"
|
||||
label="Country"
|
||||
rule>
|
||||
<vn-autocomplete vn-id="country" vn-one ng-model="$ctrl.client.countryFk" data="countries"
|
||||
show-field="country" value-field="id" label="Country" rule>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Active"
|
||||
ng-model="$ctrl.client.isActive">
|
||||
<vn-check vn-one label="Active" ng-model="$ctrl.client.isActive">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Frozen"
|
||||
ng-model="$ctrl.client.isFreezed">
|
||||
<vn-check vn-one label="Frozen" ng-model="$ctrl.client.isFreezed">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Has to invoice"
|
||||
ng-model="$ctrl.client.hasToInvoice">
|
||||
<vn-check vn-one label="Has to invoice" ng-model="$ctrl.client.hasToInvoice">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Vies"
|
||||
ng-model="$ctrl.client.isVies">
|
||||
<vn-check vn-one label="Vies" ng-model="$ctrl.client.isVies">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Notify by email"
|
||||
ng-model="$ctrl.client.isToBeMailed">
|
||||
<vn-check vn-one label="Notify by email" ng-model="$ctrl.client.isToBeMailed">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Invoice by address"
|
||||
ng-model="$ctrl.client.hasToInvoiceByAddress">
|
||||
<vn-check vn-one label="Invoice by address" ng-model="$ctrl.client.hasToInvoiceByAddress">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Is equalizated"
|
||||
ng-model="$ctrl.client.isEqualizated"
|
||||
<vn-check vn-one label="Is equalizated" ng-model="$ctrl.client.isEqualizated"
|
||||
info="In order to invoice, this field is not consulted, but the consignee's ET. When modifying this field if the invoice by address option is not checked, the change will be automatically propagated to all addresses, otherwise the user will be asked if he wants to propagate it or not."
|
||||
on-change="$ctrl.onChangeEqualizated(value)">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Verified data"
|
||||
ng-model="$ctrl.client.isTaxDataChecked"
|
||||
vn-acl="salesAssistant">
|
||||
<vn-check vn-one label="Verified data" ng-model="$ctrl.client.isTaxDataChecked" vn-acl="salesAssistant">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Incoterms authorization"
|
||||
ng-model="$ctrl.client.hasIncoterms"
|
||||
<vn-check vn-one label="Incoterms authorization" ng-model="$ctrl.client.hasIncoterms"
|
||||
vn-acl="administrative">
|
||||
</vn-check>
|
||||
<vn-check vn-one label="Electronic invoice" ng-model="$ctrl.client.hasElectronicInvoice"
|
||||
vn-acl="administrative">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-submit
|
||||
disabled="!watcher.dataChanged()"
|
||||
label="Save">
|
||||
<vn-submit disabled="!watcher.dataChanged()" label="Save">
|
||||
</vn-submit>
|
||||
<vn-button
|
||||
class="cancel"
|
||||
label="Undo changes"
|
||||
disabled="!watcher.dataChanged()"
|
||||
<vn-button class="cancel" label="Undo changes" disabled="!watcher.dataChanged()"
|
||||
ng-click="watcher.loadOriginalData()">
|
||||
</vn-button>
|
||||
</vn-button-bar>
|
||||
</form>
|
||||
<vn-confirm
|
||||
vn-id="propagate-isEqualizated"
|
||||
question="You changed the equalization tax"
|
||||
message="Do you want to spread the change?"
|
||||
on-accept="$ctrl.onAcceptEt()">
|
||||
<vn-confirm vn-id="propagate-isEqualizated" question="You changed the equalization tax"
|
||||
message="Do you want to spread the change?" on-accept="$ctrl.onAcceptEt()">
|
||||
</vn-confirm>
|
||||
<vn-confirm
|
||||
vn-id="confirm-duplicatedClient"
|
||||
message="Found a client with this data"
|
||||
<vn-confirm vn-id="confirm-duplicatedClient" message="Found a client with this data"
|
||||
on-accept="$ctrl.onAcceptDuplication()">
|
||||
</vn-confirm>
|
||||
<!-- New postcode dialog -->
|
||||
<vn-geo-postcode
|
||||
vn-id="postcode"
|
||||
on-response="$ctrl.onResponse($response)">
|
||||
<vn-geo-postcode vn-id="postcode" on-response="$ctrl.onResponse($response)">
|
||||
</vn-geo-postcode>
|
|
@ -10,4 +10,5 @@ Sage tax type: Tipo de impuesto Sage
|
|||
Sage transaction type: Tipo de transacción Sage
|
||||
Previous client: Cliente anterior
|
||||
In case of a company succession, specify the grantor company: En el caso de que haya habido una sucesión de empresa, indicar la empresa cedente
|
||||
Incoterms authorization: Autorización incoterms
|
||||
Incoterms authorization: Autorización incoterms
|
||||
Electronic invoice: Factura electrónica
|
||||
|
|
|
@ -21,3 +21,4 @@ Total net: Total neto
|
|||
Total stems: Total tallos
|
||||
Show agricultural invoice as PDF: Ver factura agrícola como PDF
|
||||
Send agricultural invoice as PDF: Enviar factura agrícola como PDF
|
||||
New InvoiceIn: Nueva Factura
|
|
@ -64,7 +64,7 @@ describe('InvoiceOut filter()', () => {
|
|||
const invoiceOut = await models.InvoiceOut.findById(1, null, options);
|
||||
await invoiceOut.updateAttribute('hasPdf', true, options);
|
||||
|
||||
const result = await models.InvoiceOut.filter(ctx, {}, options);
|
||||
const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options);
|
||||
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
concept: concept
|
||||
quantity: quantity
|
||||
price: price
|
||||
discount: discount
|
||||
reserved: reserved
|
||||
isPicked: is picked
|
||||
created: created
|
||||
originalQuantity: original quantity
|
||||
itemFk: item
|
||||
ticketFk: ticket
|
||||
saleFk: sale
|
|
@ -0,0 +1,11 @@
|
|||
concept: concepto
|
||||
quantity: cantidad
|
||||
price: precio
|
||||
discount: descuento
|
||||
reserved: reservado
|
||||
isPicked: esta seleccionado
|
||||
created: creado
|
||||
originalQuantity: cantidad original
|
||||
itemFk: artículo
|
||||
ticketFk: ticket
|
||||
saleFk: línea
|
|
@ -0,0 +1,22 @@
|
|||
shipped: shipped
|
||||
landed: landed
|
||||
nickname: nickname
|
||||
location: location
|
||||
solution: solution
|
||||
packages: packages
|
||||
updated: updated
|
||||
isDeleted: is deleted
|
||||
priority: priority
|
||||
zoneFk: zone
|
||||
zonePrice: zone price
|
||||
zoneBonus: zone bonus
|
||||
totalWithVat: total with vat
|
||||
totalWithoutVat: total without vat
|
||||
clientFk: client
|
||||
warehouseFk: warehouse
|
||||
refFk: reference
|
||||
addressFk: address
|
||||
routeFk: route
|
||||
companyFk: company
|
||||
agencyModeFk: agency
|
||||
ticketFk: ticket
|
|
@ -0,0 +1,22 @@
|
|||
shipped: fecha salida
|
||||
landed: fecha entrega
|
||||
nickname: alias
|
||||
location: ubicación
|
||||
solution: solución
|
||||
packages: embalajes
|
||||
updated: fecha última actualización
|
||||
isDeleted: esta eliminado
|
||||
priority: prioridad
|
||||
zoneFk: zona
|
||||
zonePrice: precio zona
|
||||
zoneBonus: bonus zona
|
||||
totalWithVat: total con IVA
|
||||
totalWithoutVat: total sin IVA
|
||||
clientFk: cliente
|
||||
warehouseFk: almacén
|
||||
refFk: referencia
|
||||
addressFk: dirección
|
||||
routeFk: ruta
|
||||
companyFk: empresa
|
||||
agencyModeFk: agencia
|
||||
ticketFk: ticket
|
|
@ -88,6 +88,11 @@ module.exports = Self => {
|
|||
type: 'boolean',
|
||||
description: `Whether to show only tickets with route`
|
||||
},
|
||||
{
|
||||
arg: 'hasInvoice',
|
||||
type: 'boolean',
|
||||
description: `Whether to show only tickets with invoice`
|
||||
},
|
||||
{
|
||||
arg: 'pending',
|
||||
type: 'boolean',
|
||||
|
@ -199,6 +204,10 @@ module.exports = Self => {
|
|||
if (value == true)
|
||||
return {'t.routeFk': {neq: null}};
|
||||
return {'t.routeFk': null};
|
||||
case 'hasInvoice':
|
||||
if (value == true)
|
||||
return {'t.refFk': {neq: null}};
|
||||
return {'t.refFk': null};
|
||||
case 'pending':
|
||||
return {'st.isNotValidated': value};
|
||||
case 'id':
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('ticket filter()', () => {
|
|||
const filter = {};
|
||||
const result = await models.Ticket.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(6);
|
||||
expect(result.length).toBeGreaterThan(3);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -278,4 +278,42 @@ describe('ticket filter()', () => {
|
|||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the tickets with the invoice on false', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 9}}, args: {hasInvoice: true}};
|
||||
const filter = {};
|
||||
const result = await models.Ticket.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(6);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the tickets with the invoice on null', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 9}}, args: {hasInvoice: null}};
|
||||
const filter = {};
|
||||
const result = await models.Ticket.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toBeGreaterThanOrEqual(27);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,7 +35,8 @@ class Controller extends ModuleCard {
|
|||
'credit',
|
||||
'email',
|
||||
'phone',
|
||||
'mobile'
|
||||
'mobile',
|
||||
'hasElectronicInvoice',
|
||||
],
|
||||
include: {
|
||||
relation: 'salesPersonUser',
|
||||
|
|
|
@ -65,7 +65,8 @@ class Controller extends Section {
|
|||
'credit',
|
||||
'email',
|
||||
'phone',
|
||||
'mobile'
|
||||
'mobile',
|
||||
'hasElectronicInvoice',
|
||||
],
|
||||
include: {
|
||||
relation: 'salesPersonUser',
|
||||
|
@ -243,6 +244,23 @@ class Controller extends Section {
|
|||
|
||||
makeInvoice() {
|
||||
const params = {ticketsIds: [this.id]};
|
||||
/*
|
||||
This should call the notification sistem to insert a new notification
|
||||
in te queue, yet to check how to handle user permissions,
|
||||
as of 08-11-2022 every employee can insert a new notification in the queue
|
||||
|
||||
*/
|
||||
const client = this.ticket.client;
|
||||
|
||||
if (client.hasElectronicInvoice) {
|
||||
this.$http.post(`NotificationQueues`, {
|
||||
notificationFk: 'invoiceElectronic',
|
||||
authorFk: client.id,
|
||||
}).then(a => {
|
||||
this.vnApp.showSuccess(this.$t('Invoice sent'));
|
||||
});
|
||||
}
|
||||
|
||||
return this.$http.post(`Tickets/makeInvoice`, params)
|
||||
.then(() => this.reload())
|
||||
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
|
||||
|
|
|
@ -9,5 +9,6 @@ Send CSV Delivery Note: Enviar albarán en CSV
|
|||
Send PDF Delivery Note: Enviar albarán en PDF
|
||||
Show Proforma: Ver proforma
|
||||
Refund all: Abonar todo
|
||||
Invoice sent: Factura enviada
|
||||
The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}"
|
||||
Transfer client: Transferir cliente
|
|
@ -39,7 +39,8 @@ class Controller extends Descriptor {
|
|||
'name',
|
||||
'isActive',
|
||||
'isFreezed',
|
||||
'isTaxDataChecked'
|
||||
'isTaxDataChecked',
|
||||
'hasElectronicInvoice',
|
||||
],
|
||||
include: {
|
||||
relation: 'salesPersonUser',
|
||||
|
|
|
@ -141,6 +141,11 @@
|
|||
ng-model="filter.hasRoute"
|
||||
triple-state="true">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Has invoice"
|
||||
ng-model="filter.hasInvoice"
|
||||
triple-state="true">
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg vn-pb-lg vn-mt-lg">
|
||||
<vn-submit label="Search"></vn-submit>
|
||||
|
|
|
@ -13,6 +13,7 @@ Grouped States: Estado agrupado
|
|||
Days onward: Días adelante
|
||||
With problems: Con problemas
|
||||
Has route: Con ruta
|
||||
Has invoice: Con factura
|
||||
Pending: Pendiente
|
||||
FREE: Libre
|
||||
DELIVERED: Servido
|
||||
|
|
|
@ -59,23 +59,23 @@
|
|||
</span>
|
||||
</div>
|
||||
<vn-label-value
|
||||
label="Closing"
|
||||
label="Closing"
|
||||
value="{{::row.hour | date:'HH:mm'}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Traveling days"
|
||||
label="Traveling days"
|
||||
value="{{::row.travelingDays}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Price"
|
||||
label="Price"
|
||||
value="{{::row.price | currency:'EUR':2}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Bonus"
|
||||
label="Bonus"
|
||||
value="{{::row.bonus | currency:'EUR':2}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value
|
||||
label="Max m³"
|
||||
label="Max m³"
|
||||
value="{{::row.m3Max}}">
|
||||
</vn-label-value>
|
||||
</vn-item-section>
|
||||
|
@ -97,7 +97,7 @@
|
|||
vn-bind="+"
|
||||
fixed-bottom-right>
|
||||
</vn-float-button>
|
||||
<vn-dialog
|
||||
<vn-dialog
|
||||
vn-id="includeDialog"
|
||||
on-response="$ctrl.onIncludeResponse($response)"
|
||||
message="{{$ctrl.isNew ? 'Add event' : 'Edit event'}}">
|
||||
|
@ -193,15 +193,15 @@
|
|||
</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
||||
<vn-confirm
|
||||
<vn-confirm
|
||||
vn-id="confirm"
|
||||
message="This item will be deleted"
|
||||
question="Are you sure you want to continue?">
|
||||
</vn-confirm>
|
||||
<vn-dialog
|
||||
<vn-dialog
|
||||
vn-id="excludeDialog"
|
||||
on-response="$ctrl.onExcludeResponse($response)"
|
||||
message="{{$ctrl.isNew ? 'Exclusion' : 'Edit exclusion'}}"
|
||||
message="{{$ctrl.isNew ? 'Add exclusion' : 'Edit exclusion'}}"
|
||||
on-open="$ctrl.onSearch($params)"
|
||||
on-close="$ctrl.resetExclusions()">
|
||||
<tpl-body>
|
||||
|
@ -220,7 +220,7 @@
|
|||
<vn-radio
|
||||
ng-model="$ctrl.excludeSelected.type"
|
||||
label="Specific locations"
|
||||
on-change="$ctrl.onSearch($params)"
|
||||
on-change="$ctrl.onSearch($params)"
|
||||
val="specificLocations">
|
||||
</vn-radio>
|
||||
</vn-vertical>
|
||||
|
@ -248,7 +248,7 @@
|
|||
ng-model="item.checked"
|
||||
ng-click="$event.preventDefault()"
|
||||
on-change="$ctrl.onItemCheck(item.id, value)"
|
||||
label="{{::item.name}}">
|
||||
label="{{::item.name}}">
|
||||
</vn-check>
|
||||
</vn-treeview>
|
||||
</div>
|
||||
|
|
|
@ -8,3 +8,5 @@ All: Todo
|
|||
Specific locations: Localizaciones concretas
|
||||
Locations where it is not distributed: Localizaciones en las que no se reparte
|
||||
You must select a location: Debes seleccionar una localización
|
||||
Add exclusion: Añadir exclusión
|
||||
Edit exclusion: Editar exclusión
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue