Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3963-ticket-advance

This commit is contained in:
Alexandre Riera 2022-11-25 10:25:46 +01:00
commit 8fec0670ea
36 changed files with 19983 additions and 214 deletions

View File

@ -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;
};
};

View File

@ -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;
});
};

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
VALUES ('NotificationQueue','*','*','ALLOW','ROLE','employee');

View File

@ -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

View File

@ -0,0 +1 @@
insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoiceElectronic', 'A electronic invoice has been generated');

View File

@ -0,0 +1,6 @@
UPDATE
`vn`.`client`
SET
hasElectronicInvoice = TRUE
WHERE
businessTypeFk = 'officialOrganism';

View File

@ -0,0 +1 @@
Delete this file

View 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);

View File

@ -19,6 +19,7 @@
-- Current Database: `account`
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `account` /*!40100 DEFAULT CHARACTER SET utf8mb3 */;
USE `account`;

View File

@ -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));

View File

@ -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=="
}
}
}

View File

@ -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;
}

View File

@ -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
};
}

View File

@ -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;

View File

@ -142,7 +142,11 @@
},
"salesPersonFk": {
"type": "number"
},
"hasElectronicInvoice": {
"type": "boolean"
}
},
"relations": {
"account": {

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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':

View File

@ -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;
}
});
});

View File

@ -35,7 +35,8 @@ class Controller extends ModuleCard {
'credit',
'email',
'phone',
'mobile'
'mobile',
'hasElectronicInvoice',
],
include: {
relation: 'salesPersonUser',

View File

@ -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')));

View File

@ -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

View File

@ -39,7 +39,8 @@ class Controller extends Descriptor {
'name',
'isActive',
'isFreezed',
'isTaxDataChecked'
'isTaxDataChecked',
'hasElectronicInvoice',
],
include: {
relation: 'salesPersonUser',

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

19651
package-lock.json generated

File diff suppressed because it is too large Load Diff