5914-transferInvoiceOut #1761

Merged
jorgep merged 55 commits from 5914-transferInvoiceOut into dev 2023-10-26 12:39:11 +00:00
57 changed files with 957 additions and 230 deletions
Showing only changes of commit 7ca6c6b96d - Show all commits

View File

@ -5,7 +5,8 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2330.01] - 2023-07-27
## [2334.01] - 2023-08-24
### Added ### Added
@ -14,6 +15,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
## [2332.01] - 2023-08-09
### Added
- (Trabajadores -> Gestión documental) Soporte para Docuware
- (General -> Agencia) Soporte para Viaexpress
### Changed
- (General -> Tickets) Devuelve el motivo por el cual no es editable
- (Desplegables -> Trabajadores) Mejorados
### Fixed
## [2330.01] - 2023-07-27 ## [2330.01] - 2023-07-27
### Added ### Added
@ -24,7 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- (General -> Iconos) Añadidos nuevos iconos - (General -> Iconos) Añadidos nuevos iconos
- (Clientes -> Razón social) Nuevas restricciones por pais - (Clientes -> Razón social) Permite crear clientes con la misma razón social según el país
### Fixed ### Fixed

View File

@ -47,7 +47,7 @@ module.exports = Self => {
const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions); const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
const userToUpdate = await Self.findById(id, { const userToUpdate = await Self.findById(id, {
fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'], fields: ['id', 'name', 'hasGrant', 'roleFk', 'password', 'email'],
include: { include: {
relation: 'role', relation: 'role',
scope: { scope: {

View File

@ -20,7 +20,7 @@ module.exports = function(Self) {
Self.validatesFormatOf('email', { Self.validatesFormatOf('email', {
message: 'Invalid email', message: 'Invalid email',
allowNull: true, allowNull: true,
allowBlank: true, allowBlank: false,
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/ with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
}); });
@ -115,6 +115,14 @@ module.exports = function(Self) {
Self.validateLogin = async function(user, password) { Self.validateLogin = async function(user, password) {
let loginInfo = Object.assign({password}, Self.userUses(user)); let loginInfo = Object.assign({password}, Self.userUses(user));
token = await Self.login(loginInfo, 'user'); token = await Self.login(loginInfo, 'user');
const userToken = await token.user.get();
try {
await Self.app.models.Account.sync(userToken.name, password);
} catch (err) {
console.warn(err);
}
return {token: token.id, ttl: token.ttl}; return {token: token.id, ttl: token.ttl};
}; };

View File

@ -0,0 +1,87 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`client_create`(
vFirstname VARCHAR(50),
vSurnames VARCHAR(50),
vFi VARCHAR(9),
vAddress TEXT,
vPostcode CHAR(5),
vCity VARCHAR(25),
vProvinceFk SMALLINT(5),
vCompanyFk SMALLINT(5),
vPhone VARCHAR(11),
vEmail VARCHAR(255),
vUserFk INT
)
BEGIN
/**
* Create new client
*
* @params vFirstname firstName
* @params vSurnames surnames
* @params vFi company code from accounting transactions
* @params vAddress address
* @params vPostcode postCode
* @params vCity city
* @params vProvinceFk province
* @params vCompanyFk company in which he has become a client
* @params vPhone telephone number
* @params vEmail email address
* @params vUserFk user id
*/
DECLARE vPayMethodFk INT;
DECLARE vDueDay INT;
DECLARE vDefaultCredit DECIMAL(10, 2);
DECLARE vIsTaxDataChecked TINYINT(1);
DECLARE vHasCoreVnl BOOLEAN;
DECLARE vMandateTypeFk INT;
SELECT defaultPayMethodFk,
defaultDueDay,
defaultCredit,
defaultIsTaxDataChecked,
defaultHasCoreVnl,
defaultMandateTypeFk
INTO vPayMethodFk,
vDueDay,
vDefaultCredit,
vIsTaxDataChecked,
vHasCoreVnl,
vMandateTypeFk
FROM clientConfig;
INSERT INTO `client`
SET id = vUserFk,
name = CONCAT(vFirstname, ' ', vSurnames),
street = vAddress,
fi = TRIM(vFi),
phone = vPhone,
email = vEmail,
provinceFk = vProvinceFk,
city = vCity,
postcode = vPostcode,
socialName = UPPER(CONCAT(vSurnames, ' ', vFirstname)),
payMethodFk = vPayMethodFk,
dueDay = vDueDay,
credit = vDefaultCredit,
isTaxDataChecked = vIsTaxDataChecked,
hasCoreVnl = vHasCoreVnl,
isEqualizated = FALSE
ON duplicate KEY UPDATE
payMethodFk = vPayMethodFk,
dueDay = vDueDay,
credit = vDefaultCredit,
isTaxDataChecked = vIsTaxDataChecked,
hasCoreVnl = vHasCoreVnl,
isActive = TRUE;
INSERT INTO mandate (clientFk, companyFk, mandateTypeFk)
SELECT vUserFk, vCompanyFk, vMandateTypeFk
WHERE NOT EXISTS (
SELECT id
FROM mandate
WHERE clientFk = vUserFk
AND companyFk = vCompanyFk
AND mandateTypeFk = vMandateTypeFk
);
END$$
DELIMITER ;

View File

View File

@ -360,13 +360,13 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`)
INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`) INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`)
VALUES VALUES
(1101, 'Bruce Wayne', '84612325V', 'Batman', 'Alfred', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
(1102, 'Petter Parker', '87945234L', 'Spider man', 'Aunt May', '20 Ingram Street, Queens, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
(1103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street, Apartament 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street, Apartament 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
(1104, 'Tony Stark', '06089160W', 'Iron man', 'Pepper Potts', '10880 Malibu Point, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1104, 'Tony Stark', '06089160W', 'Iron man', 'Pepper Potts', '10880 Malibu Point, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
(1105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist'), (1105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist'),
(1106, 'DavidCharlesHaller', '53136686Q', 'Legion', 'Charles Xavier', 'City of New York, New York, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist'), (1106, 'DavidCharlesHaller', '53136686Q', 'LEGION', 'Charles Xavier', 'CITY OF NEW YORK, NEW YORK, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist'),
(1107, 'Hank Pym', '09854837G', 'Ant man', 'Hawk', 'Anthill, San Francisco, California', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist'), (1107, 'Hank Pym', '09854837G', 'ANT MAN', 'Hawk', 'ANTHILL, SAN FRANCISCO, CALIFORNIA', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist'),
(1108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist'), (1108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist'),
(1109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist'), (1109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist'),
(1110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist'), (1110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist'),

View File

@ -73,8 +73,8 @@ describe('Client create path', () => {
it(`should attempt to create a new user with all it's data but wrong email`, async() => { it(`should attempt to create a new user with all it's data but wrong email`, async() => {
await page.write(selectors.createClientView.name, 'Carol Danvers'); await page.write(selectors.createClientView.name, 'Carol Danvers');
await page.write(selectors.createClientView.socialName, 'AVG tax'); await page.write(selectors.createClientView.socialName, 'AVG TAX');
await page.write(selectors.createClientView.street, 'Many places'); await page.write(selectors.createClientView.street, 'MANY PLACES');
await page.clearInput(selectors.createClientView.email); await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'incorrect email format'); await page.write(selectors.createClientView.email, 'incorrect email format');
await page.waitToClick(selectors.createClientView.createButton); await page.waitToClick(selectors.createClientView.createButton);

View File

@ -61,7 +61,7 @@ describe('Client Edit fiscalData path', () => {
await page.clearInput(selectors.clientFiscalData.fiscalId); await page.clearInput(selectors.clientFiscalData.fiscalId);
await page.write(selectors.clientFiscalData.fiscalId, 'INVALID!'); await page.write(selectors.clientFiscalData.fiscalId, 'INVALID!');
await page.clearInput(selectors.clientFiscalData.address); await page.clearInput(selectors.clientFiscalData.address);
await page.write(selectors.clientFiscalData.address, 'Somewhere edited'); await page.write(selectors.clientFiscalData.address, 'SOMEWHERE EDITED');
await page.autocompleteSearch(selectors.clientFiscalData.country, 'España'); await page.autocompleteSearch(selectors.clientFiscalData.country, 'España');
await page.autocompleteSearch(selectors.clientFiscalData.province, 'Province one'); await page.autocompleteSearch(selectors.clientFiscalData.province, 'Province one');
await page.clearInput(selectors.clientFiscalData.city); await page.clearInput(selectors.clientFiscalData.city);
@ -190,7 +190,7 @@ describe('Client Edit fiscalData path', () => {
const verifiedData = await page.checkboxState(selectors.clientFiscalData.verifiedDataCheckbox); const verifiedData = await page.checkboxState(selectors.clientFiscalData.verifiedDataCheckbox);
expect(fiscalId).toEqual('94980061C'); expect(fiscalId).toEqual('94980061C');
expect(address).toEqual('Somewhere edited'); expect(address).toEqual('SOMEWHERE EDITED');
expect(postcode).toContain('46000'); expect(postcode).toContain('46000');
expect(sageTax).toEqual('Operaciones no sujetas'); expect(sageTax).toEqual('Operaciones no sujetas');
expect(sageTransaction).toEqual('Regularización de inversiones'); expect(sageTransaction).toEqual('Regularización de inversiones');

View File

@ -28,7 +28,7 @@ describe('Client lock verified data path', () => {
it('should edit the social name', async() => { it('should edit the social name', async() => {
await page.waitForSelector(selectors.clientFiscalData.socialName); await page.waitForSelector(selectors.clientFiscalData.socialName);
await page.clearInput(selectors.clientFiscalData.socialName); await page.clearInput(selectors.clientFiscalData.socialName);
await page.write(selectors.clientFiscalData.socialName, 'Captain America Civil War'); await page.write(selectors.clientFiscalData.socialName, 'CAPTAIN AMERICA CIVIL WAR');
await page.waitToClick(selectors.clientFiscalData.saveButton); await page.waitToClick(selectors.clientFiscalData.saveButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
@ -39,7 +39,7 @@ describe('Client lock verified data path', () => {
await page.reloadSection('client.card.fiscalData'); await page.reloadSection('client.card.fiscalData');
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
expect(result).toEqual('Captain America Civil War'); expect(result).toEqual('CAPTAIN AMERICA CIVIL WAR');
}); });
}); });
@ -88,7 +88,7 @@ describe('Client lock verified data path', () => {
await page.reloadSection('client.card.fiscalData'); await page.reloadSection('client.card.fiscalData');
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
expect(result).toEqual('Ant man and the Wasp'); expect(result).toEqual('ANT MAN AND THE WASP');
}); });
}); });
@ -142,7 +142,7 @@ describe('Client lock verified data path', () => {
await page.reloadSection('client.card.fiscalData'); await page.reloadSection('client.card.fiscalData');
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
expect(result).toEqual('new social name edition'); expect(result).toEqual('NEW SOCIAL NAME EDITION');
}); });
}); });

View File

@ -36,7 +36,7 @@ describe('Client summary path', () => {
it('should display fiscal address details', async() => { it('should display fiscal address details', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.street, 'innerText'); const result = await page.waitToGetProperty(selectors.clientSummary.street, 'innerText');
expect(result).toContain('20 Ingram Street'); expect(result).toContain('20 INGRAM STREET');
}); });
it('should display some fiscal data', async() => { it('should display some fiscal data', async() => {

View File

@ -23,7 +23,7 @@ describe('Worker create path', () => {
await page.write(selectors.workerCreate.fi, '78457139E'); await page.write(selectors.workerCreate.fi, '78457139E');
await page.write(selectors.workerCreate.phone, '12356789'); await page.write(selectors.workerCreate.phone, '12356789');
await page.write(selectors.workerCreate.postcode, '46680'); await page.write(selectors.workerCreate.postcode, '46680');
await page.write(selectors.workerCreate.street, 'S/ Doomstadt'); await page.write(selectors.workerCreate.street, 'S/ DOOMSTADT');
await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com'); await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com');
await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332'); await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332');

View File

@ -339,8 +339,9 @@ export default class SmartTable extends Component {
if (!header) return; if (!header) return;
const tbody = this.element.querySelector('tbody'); const tbody = this.element.querySelector('tbody');
const columns = header.querySelectorAll('th'); if (!tbody) return;
const columns = header.querySelectorAll('th');
const hasSearchRow = tbody.querySelector('tr#searchRow'); const hasSearchRow = tbody.querySelector('tr#searchRow');
if (hasSearchRow) { if (hasSearchRow) {
if (this.$inputsScope) if (this.$inputsScope)

View File

@ -179,6 +179,8 @@
"You can not use the same password": "You can not use the same password", "You can not use the same password": "You can not use the same password",
"Valid priorities": "Valid priorities: %d", "Valid priorities": "Valid priorities: %d",
"Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}", "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}",
"Social name should be uppercase": "Social name should be uppercase",
"Street should be uppercase": "Street should be uppercase",
"You don't have enough privileges.": "You don't have enough privileges.", "You don't have enough privileges.": "You don't have enough privileges.",
"This ticket is locked.": "This ticket is locked.", "This ticket is locked.": "This ticket is locked.",
"This ticket is not editable.": "This ticket is not editable.", "This ticket is not editable.": "This ticket is not editable.",

View File

@ -305,12 +305,19 @@
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado", "The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
"Valid priorities": "Prioridades válidas: %d", "Valid priorities": "Prioridades válidas: %d",
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado",
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
"You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado",
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado",
"You don't have enough privileges.": "No tienes suficientes permisos.", "You don't have enough privileges.": "No tienes suficientes permisos.",
"This ticket is locked.": "Este ticket está bloqueado.", "This ticket is locked.": "Este ticket está bloqueado.",
"This ticket is not editable.": "Este ticket no es editable.", "This ticket is not editable.": "Este ticket no es editable.",
"The ticket doesn't exist.": "No existe el ticket.", "The ticket doesn't exist.": "No existe el ticket.",
<<<<<<< HEAD
"There are missing fields.": "There are missing fields." "There are missing fields.": "There are missing fields."
} }
=======
"Social name should be uppercase": "La razón social debe ir en mayúscula",
"Street should be uppercase": "La dirección fiscal debe ir en mayúscula"
}
>>>>>>> ce78fc8e5dd383b3c6457fe5f78a45b21b246858

View File

@ -7,8 +7,8 @@ describe('Client Create', () => {
email: 'Deadpool@marvel.com', email: 'Deadpool@marvel.com',
fi: '16195279J', fi: '16195279J',
name: 'Wade', name: 'Wade',
socialName: 'Deadpool Marvel', socialName: 'DEADPOOL MARVEL',
street: 'Wall Street', street: 'WALL STREET',
city: 'New York', city: 'New York',
businessTypeFk: 'florist', businessTypeFk: 'florist',
provinceFk: 1 provinceFk: 1

View File

@ -36,6 +36,20 @@ module.exports = Self => {
min: 3, max: 10 min: 3, max: 10
}); });
Self.validatesFormatOf('street', {
message: 'Street should be uppercase',
allowNull: false,
allowBlank: false,
with: /^[^a-z]*$/
});
Self.validatesFormatOf('socialName', {
message: 'Social name should be uppercase',
allowNull: false,
allowBlank: false,
with: /^[^a-z]*$/
});
Self.validateAsync('socialName', socialNameIsUnique, { Self.validateAsync('socialName', socialNameIsUnique, {
message: 'The company name must be unique' message: 'The company name must be unique'
}); });

View File

@ -33,6 +33,7 @@
ng-model="$ctrl.client.socialName" ng-model="$ctrl.client.socialName"
info="Only letters, numbers and spaces can be used" info="Only letters, numbers and spaces can be used"
required="true" required="true"
ng-keyup="$ctrl.client.socialName = $ctrl.client.socialName.toUpperCase()"
rule> rule>
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
@ -46,6 +47,7 @@
vn-two vn-two
label="Street" label="Street"
ng-model="$ctrl.client.street" ng-model="$ctrl.client.street"
ng-keyup="$ctrl.client.street = $ctrl.client.street.toUpperCase()"
rule> rule>
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>

View File

@ -39,7 +39,7 @@
label="Recovery email" label="Recovery email"
ng-model="$ctrl.account.email" ng-model="$ctrl.account.email"
info="This email is used for user to regain access their account." info="This email is used for user to regain access their account."
rule="VnUser.name"> rule="VnUser.email">
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>

View File

@ -0,0 +1,20 @@
name: invoice in
columns:
id: id
serialNumber: serial number
serial: serial
supplierFk: supplier
issued: issued
supplierRef: supplierRef
isBooked: is booked
currencyFk: currency
created: created
companyFk: company
docFk: document
booked: booked
operated: operated
bookEntried: book entried
isVatDeductible: is VAT deductible
withholdingSageFk: withholding
expenceFkDeductible: expence deductible
editorFk: editor

View File

@ -0,0 +1,20 @@
name: factura recibida
columns:
id: id
serialNumber: número de serie
serial: serie
supplierFk: proveedor
issued: fecha emisión
supplierRef: referéncia proveedor
isBooked: facturado
currencyFk: moneda
created: creado
companyFk: empresa
docFk: documento
booked: fecha contabilización
operated: fecha entrega
bookEntried: fecha asiento
isVatDeductible: impuesto deducible
withholdingSageFk: código de retención
expenceFkDeductible: gasto deducible
editorFk: editor

View File

@ -0,0 +1,9 @@
name: invoice in due day
columns:
id: id
invoiceInFk: invoice in
dueDated: due date
bankFk: bank
amount: amount
foreignValue : foreign amount
created: created

View File

@ -0,0 +1,9 @@
name: vencimientos factura recibida
columns:
id: id
invoiceInFk: factura
dueDated: fecha vto.
bankFk: banco
amount: importe
foreignValue : importe divisa
created: creado

View File

@ -0,0 +1,12 @@
name: invoice in tax
columns:
id: id
invoiceInFk: invoice in
taxCodeFk: tax
taxableBase: taxable base
expenceFk: expence
foreignValue: foreign amount
taxTypeSageFk: tax type
transactionTypeSageFk: transaction type
created: created
editorFk: editor

View File

@ -0,0 +1,12 @@
name: factura recibida impuesto
columns:
id: id
invoiceInFk: factura recibida
taxCodeFk: código IVA
taxableBase: base imponible
expenceFk: código gasto
foreignValue: importe divisa
taxTypeSageFk: código impuesto
transactionTypeSageFk: código transacción
created: creado
editorFk: editor

View File

@ -2,37 +2,37 @@
<vn-auto> <vn-auto>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value5}" ng-class="::{empty: !$ctrl.item.tag5}"
title="{{::$ctrl.item.tag5}}: {{::$ctrl.item.value5}}"> title="{{::$ctrl.item.tag5}}: {{::$ctrl.item.value5}}">
{{::$ctrl.item.value5}} {{::$ctrl.item.value5}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value6}" ng-class="::{empty: !$ctrl.item.tag6}"
title="{{::$ctrl.item.tag6}}: {{::$ctrl.item.value6}}"> title="{{::$ctrl.item.tag6}}: {{::$ctrl.item.value6}}">
{{::$ctrl.item.value6}} {{::$ctrl.item.value6}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value7}" ng-class="::{empty: !$ctrl.item.tag7}"
title="{{::$ctrl.item.tag7}}: {{::$ctrl.item.value7}}"> title="{{::$ctrl.item.tag7}}: {{::$ctrl.item.value7}}">
{{::$ctrl.item.value7}} {{::$ctrl.item.value7}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value8}" ng-class="::{empty: !$ctrl.item.tag8}"
title="{{::$ctrl.item.tag8}}: {{::$ctrl.item.value8}}"> title="{{::$ctrl.item.tag8}}: {{::$ctrl.item.value8}}">
{{::$ctrl.item.value8}} {{::$ctrl.item.value8}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value9}" ng-class="::{empty: !$ctrl.item.tag9}"
title="{{::$ctrl.item.tag9}}: {{::$ctrl.item.value9}}"> title="{{::$ctrl.item.tag9}}: {{::$ctrl.item.value9}}">
{{::$ctrl.item.value9}} {{::$ctrl.item.value9}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value10}" ng-class="::{empty: !$ctrl.item.tag10}"
title="{{::$ctrl.item.tag10}}: {{::$ctrl.item.value10}}"> title="{{::$ctrl.item.tag10}}: {{::$ctrl.item.value10}}">
{{::$ctrl.item.value10}} {{::$ctrl.item.value10}}
</section> </section>

View File

@ -28,7 +28,7 @@
vn-fetched-tags { vn-fetched-tags {
& > vn-horizontal { & > vn-horizontal {
align-items: center; align-items: center;
max-width: 210px;
& > vn-auto { & > vn-auto {
flex-wrap: wrap; flex-wrap: wrap;
@ -43,17 +43,17 @@ vn-fetched-tags {
& > .inline-tag { & > .inline-tag {
color: $color-font-secondary; color: $color-font-secondary;
text-align: center; text-align: center;
font-size: .75rem; font-size: .8rem;
height: 12px; height: 13px;
padding: 1px; padding: 1px;
width: 64px; width: 64px;
min-width: 64px; min-width: 64px;
max-width: 64px; max-width: 64px;
flex: 1; flex: 1;
border: 1px solid $color-spacer; border: 1px solid $color-font-secondary;
&.empty { &.empty {
border: 1px solid $color-spacer-light; border: 1px solid darken($color-font-secondary, 30%);
} }
} }
} }

View File

@ -0,0 +1,36 @@
module.exports = Self => {
Self.remoteMethodCtx('cmr', {
description: 'Returns the cmr',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The cmr id',
http: {source: 'path'}
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: '/:id/cmr',
verb: 'GET'
}
});
Self.cmr = (ctx, id) => Self.printReport(ctx, id, 'cmr');
};

View File

@ -14,6 +14,7 @@ module.exports = Self => {
require('../methods/route/driverRouteEmail')(Self); require('../methods/route/driverRouteEmail')(Self);
require('../methods/route/sendSms')(Self); require('../methods/route/sendSms')(Self);
require('../methods/route/downloadZip')(Self); require('../methods/route/downloadZip')(Self);
require('../methods/route/cmr')(Self);
Self.validate('kmStart', validateDistance, { Self.validate('kmStart', validateDistance, {
message: 'Distance must be lesser than 1000' message: 'Distance must be lesser than 1000'
@ -28,5 +29,5 @@ module.exports = Self => {
const routeMaxKm = 1000; const routeMaxKm = 1000;
if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd) if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd)
err(); err();
} };
}; };

View File

@ -67,7 +67,7 @@ module.exports = function(Self) {
throw new UserError(`This ticket is already invoiced`); throw new UserError(`This ticket is already invoiced`);
const priceZero = ticket.totalWithVat == 0; const priceZero = ticket.totalWithVat == 0;
if (priceZero) if (ticketsIds.length == 1 && priceZero)
throw new UserError(`A ticket with an amount of zero can't be invoiced`); throw new UserError(`A ticket with an amount of zero can't be invoiced`);
}); });

View File

@ -5,177 +5,177 @@ const config = require('vn-print/core/config');
const storage = require('vn-print/core/storage'); const storage = require('vn-print/core/storage');
module.exports = async function(ctx, Self, tickets, reqArgs = {}) { module.exports = async function(ctx, Self, tickets, reqArgs = {}) {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
if (tickets.length == 0) return; if (tickets.length == 0) return;
const failedtickets = []; const failedtickets = [];
for (const ticket of tickets) { for (const ticket of tickets) {
try { try {
await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id], {userId}); await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id], {userId});
const [invoiceOut] = await Self.rawSql(` const [invoiceOut] = await Self.rawSql(`
SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued
FROM ticket t FROM ticket t
JOIN invoiceOut io ON io.ref = t.refFk JOIN invoiceOut io ON io.ref = t.refFk
JOIN company cny ON cny.id = io.companyFk JOIN company cny ON cny.id = io.companyFk
WHERE t.id = ? WHERE t.id = ?
`, [ticket.id]); `, [ticket.id]);
const mailOptions = { const mailOptions = {
overrideAttachments: true, overrideAttachments: true,
attachments: [] attachments: []
}; };
const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed; const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed;
if (invoiceOut) { if (invoiceOut) {
const args = { const args = {
reference: invoiceOut.ref, reference: invoiceOut.ref,
recipientId: ticket.clientFk, recipientId: ticket.clientFk,
recipient: ticket.recipient, recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail replyTo: ticket.salesPersonEmail
}; };
const invoiceReport = new Report('invoice', args); const invoiceReport = new Report('invoice', args);
const stream = await invoiceReport.toPdfStream(); const stream = await invoiceReport.toPdfStream();
const issued = invoiceOut.issued; const issued = invoiceOut.issued;
const year = issued.getFullYear().toString(); const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString(); const month = (issued.getMonth() + 1).toString();
const day = issued.getDate().toString(); const day = issued.getDate().toString();
const fileName = `${year}${invoiceOut.ref}.pdf`; const fileName = `${year}${invoiceOut.ref}.pdf`;
// Store invoice // Store invoice
await storage.write(stream, { await storage.write(stream, {
type: 'invoice', type: 'invoice',
path: `${year}/${month}/${day}`, path: `${year}/${month}/${day}`,
fileName: fileName fileName: fileName
}); });
await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id], {userId}); await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id], {userId});
if (isToBeMailed) { if (isToBeMailed) {
const invoiceAttachment = { const invoiceAttachment = {
filename: fileName, filename: fileName,
content: stream content: stream
}; };
if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') { if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') {
const exportation = new Report('exportation', args); const exportation = new Report('exportation', args);
const stream = await exportation.toPdfStream(); const stream = await exportation.toPdfStream();
const fileName = `CITES-${invoiceOut.ref}.pdf`; const fileName = `CITES-${invoiceOut.ref}.pdf`;
mailOptions.attachments.push({ mailOptions.attachments.push({
filename: fileName, filename: fileName,
content: stream content: stream
}); });
} }
mailOptions.attachments.push(invoiceAttachment); mailOptions.attachments.push(invoiceAttachment);
const email = new Email('invoice', args); const email = new Email('invoice', args);
await email.send(mailOptions); await email.send(mailOptions);
} }
} else if (isToBeMailed) { } else if (isToBeMailed) {
const args = { const args = {
id: ticket.id, id: ticket.id,
recipientId: ticket.clientFk, recipientId: ticket.clientFk,
recipient: ticket.recipient, recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail replyTo: ticket.salesPersonEmail
}; };
const email = new Email('delivery-note-link', args); const email = new Email('delivery-note-link', args);
await email.send(); await email.send();
} }
// Incoterms authorization // Incoterms authorization
const [{firstOrder}] = await Self.rawSql(` const [{firstOrder}] = await Self.rawSql(`
SELECT COUNT(*) as firstOrder SELECT COUNT(*) as firstOrder
FROM ticket t FROM ticket t
JOIN client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
WHERE t.clientFk = ? WHERE t.clientFk = ?
AND NOT t.isDeleted AND NOT t.isDeleted
AND c.isVies AND c.isVies
`, [ticket.clientFk]); `, [ticket.clientFk]);
if (firstOrder == 1) { if (firstOrder == 1) {
const args = { const args = {
id: ticket.clientFk, id: ticket.clientFk,
companyId: ticket.companyFk, companyId: ticket.companyFk,
recipientId: ticket.clientFk, recipientId: ticket.clientFk,
recipient: ticket.recipient, recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail replyTo: ticket.salesPersonEmail
}; };
const email = new Email('incoterms-authorization', args); const email = new Email('incoterms-authorization', args);
await email.send(); await email.send();
const [sample] = await Self.rawSql( const [sample] = await Self.rawSql(
`SELECT id `SELECT id
FROM sample FROM sample
WHERE code = 'incoterms-authorization' WHERE code = 'incoterms-authorization'
`); `);
await Self.rawSql(` await Self.rawSql(`
INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?) INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?)
`, [ticket.clientFk, sample.id, ticket.companyFk], {userId}); `, [ticket.clientFk, sample.id, ticket.companyFk], {userId});
} };
} catch (error) { } catch (error) {
// Domain not found // Domain not found
if (error.responseCode == 450) if (error.responseCode == 450)
return invalidEmail(ticket); return invalidEmail(ticket);
// Save tickets on a list of failed ids // Save tickets on a list of failed ids
failedtickets.push({ failedtickets.push({
id: ticket.id, id: ticket.id,
stacktrace: error stacktrace: error
}); });
} }
} }
// Send email with failed tickets // Send email with failed tickets
if (failedtickets.length > 0) { if (failedtickets.length > 0) {
let body = 'This following tickets have failed:<br/><br/>'; let body = 'This following tickets have failed:<br/><br/>';
for (const ticket of failedtickets) { for (const ticket of failedtickets) {
body += `Ticket: <strong>${ticket.id}</strong> body += `Ticket: <strong>${ticket.id}</strong>
<br/> <strong>${ticket.stacktrace}</strong><br/><br/>`; <br/> <strong>${ticket.stacktrace}</strong><br/><br/>`;
} }
smtp.send({ smtp.send({
to: config.app.reportEmail, to: config.app.reportEmail,
subject: '[API] Nightly ticket closure report', subject: '[API] Nightly ticket closure report',
html: body html: body
}); });
} }
async function invalidEmail(ticket) { async function invalidEmail(ticket) {
await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [ await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [
ticket.clientFk ticket.clientFk
], {userId}); ], {userId});
const oldInstance = `{"email": "${ticket.recipient}"}`; const oldInstance = `{"email": "${ticket.recipient}"}`;
const newInstance = `{"email": ""}`; const newInstance = `{"email": ""}`;
await Self.rawSql(` await Self.rawSql(`
INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance) INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [ VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [
ticket.clientFk, ticket.clientFk,
oldInstance, oldInstance,
newInstance newInstance
], {userId}); ], {userId});
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong> const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong> al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong>
porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta
o no está disponible.<br/><br/> o no está disponible.<br/><br/>
Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente. Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
Actualiza la dirección de email con una correcta.`; Actualiza la dirección de email con una correcta.`;
smtp.send({ smtp.send({
to: ticket.salesPersonEmail, to: ticket.salesPersonEmail,
subject: 'No se ha podido enviar el albarán', subject: 'No se ha podido enviar el albarán',
html: body html: body
}); });
} }
}; };

View File

@ -1,4 +1,5 @@
const loggable = require('vn-loopback/util/log'); const loggable = require('vn-loopback/util/log');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('componentUpdate', { Self.remoteMethodCtx('componentUpdate', {
@ -112,7 +113,6 @@ module.exports = Self => {
} }
try { try {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models; const models = Self.app.models;
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
await models.Ticket.isEditableOrThrow(ctx, args.id, myOptions); await models.Ticket.isEditableOrThrow(ctx, args.id, myOptions);
@ -127,11 +127,8 @@ module.exports = Self => {
args.warehouseFk, args.warehouseFk,
myOptions); myOptions);
if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk) { if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk)
const error = `You don't have privileges to change the zone`; throw new UserError(`You don't have privileges to change the zone`);
throw new UserError(error);
}
} }
if (args.isWithoutNegatives) { if (args.isWithoutNegatives) {

View File

@ -248,6 +248,7 @@ module.exports = Self => {
am.name AS agencyMode, am.name AS agencyMode,
am.id AS agencyModeFk, am.id AS agencyModeFk,
st.name AS state, st.name AS state,
st.classColor,
wk.lastName AS salesPerson, wk.lastName AS salesPerson,
ts.stateFk AS stateFk, ts.stateFk AS stateFk,
ts.alertLevel AS alertLevel, ts.alertLevel AS alertLevel,
@ -339,7 +340,8 @@ module.exports = Self => {
{'tp.isFreezed': hasProblem}, {'tp.isFreezed': hasProblem},
{'tp.risk': hasProblem}, {'tp.risk': hasProblem},
{'tp.hasTicketRequest': hasProblem}, {'tp.hasTicketRequest': hasProblem},
{'tp.itemShortage': range} {'tp.itemShortage': range},
{'tp.hasRounding': hasProblem}
]}; ]};
if (hasWhere) if (hasWhere)

View File

@ -194,7 +194,8 @@ module.exports = Self => {
{'tp.hasTicketRequest': hasProblem}, {'tp.hasTicketRequest': hasProblem},
{'tp.itemShortage': range}, {'tp.itemShortage': range},
{'tp.hasComponentLack': hasProblem}, {'tp.hasComponentLack': hasProblem},
{'tp.isTooLittle': hasProblem} {'tp.isTooLittle': hasProblem},
{'tp.hasRounding': hasProblem}
] ]
}; };

View File

@ -27,6 +27,9 @@
"code": { "code": {
"type": "string", "type": "string",
"required": false "required": false
},
"classColor": {
"type": "string"
} }
} }
} }

View File

@ -123,6 +123,12 @@
class="bright" class="bright"
icon="icon-components"> icon="icon-components">
</vn-icon> </vn-icon>
<vn-icon
ng-show="::ticket.hasRounding"
translate-attr="{title: 'Rounding'}"
class="bright"
icon="sync_problem">
</vn-icon>
</td> </td>
<td><span <td><span
ng-click="ticketDescriptor.show($event, ticket.id)" ng-click="ticketDescriptor.show($event, ticket.id)"

View File

@ -75,6 +75,12 @@
class="bright" class="bright"
icon="icon-components"> icon="icon-components">
</vn-icon> </vn-icon>
<vn-icon
ng-show="::ticket.hasRounding"
translate-attr="{title: 'Rounding'}"
class="bright"
icon="sync_problem">
</vn-icon>
</vn-td> </vn-td>
<vn-td shrink>{{::ticket.id}}</vn-td> <vn-td shrink>{{::ticket.id}}</vn-td>
<vn-td class="expendable"> <vn-td class="expendable">

View File

@ -71,8 +71,9 @@ module.exports = Self => {
'Stored on': 'created', 'Stored on': 'created',
'Document ID': 'id' 'Document ID': 'id'
}; };
workerDocuware = workerDocuware =
await models.Docuware.getById('hr', worker.lastName + worker.firstName, docuwareParse) ?? []; await models.Docuware.getById('hr', worker.lastName + ' ' + worker.firstName, docuwareParse) ?? [];
for (document of workerDocuware) { for (document of workerDocuware) {
const defaultData = { const defaultData = {
file: 'dw' + document.id + '.png', file: 'dw' + document.id + '.png',

View File

@ -36,9 +36,9 @@ module.exports = Self => {
if (isSubordinate === false) if (isSubordinate === false)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
const subordinate = await Worker.findById(ctx.args.workerFk); const subordinate = await Worker.findById(ctx.args.workerFk, {fields: ['id']});
filter = mergeFilters(filter, {where: { filter = mergeFilters(filter, {where: {
userFk: subordinate.userFk userFk: subordinate.id
}}); }});
return Self.find(filter); return Self.find(filter);

View File

@ -20,11 +20,11 @@ describe('Worker new', () => {
const employeeId = 1; const employeeId = 1;
const defaultWorker = { const defaultWorker = {
fi: '78457139E', fi: '78457139E',
name: 'defaultWorker', name: 'DEFAULTERWORKER',
firstName: 'default', firstName: 'DEFAULT',
lastNames: 'worker', lastNames: 'WORKER',
email: 'defaultWorker@mydomain.com', email: 'defaultWorker@mydomain.com',
street: 'S/ defaultWorkerStreet', street: 'S/ DEFAULTWORKERSTREET',
city: 'defaultWorkerCity', city: 'defaultWorkerCity',
provinceFk: 1, provinceFk: 1,
companyFk: 442, companyFk: 442,

View File

@ -25,8 +25,7 @@
"type" : "string" "type" : "string"
}, },
"userFk": { "userFk": {
"type" : "number", "type" : "number"
"required": true
}, },
"bossFk": { "bossFk": {
"type" : "number" "type" : "number"

View File

@ -32,6 +32,28 @@ class Controller extends Descriptor {
this.vnApp.showSuccess(this.$t('Department deleted.')); this.vnApp.showSuccess(this.$t('Department deleted.'));
}); });
} }
loadData() {
const filter = {
fields: ['id', 'name', 'code', 'workerFk', 'isProduction', 'chatName',
'isTeleworking', 'notificationEmail', 'hasToRefill', 'hasToSendMail', 'hasToMistake', 'clientFk'],
include: [
{relation: 'client',
scope: {
fields: ['id', 'name']
}},
{
relation: 'worker',
scope: {
fields: ['id', 'firstName', 'lastName']
}
}
]
};
return this.getData(`Departments/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
} }
Controller.$inject = ['$element', '$scope', '$rootScope']; Controller.$inject = ['$element', '$scope', '$rootScope'];

View File

@ -4,7 +4,7 @@
filter="::$ctrl.filter" filter="::$ctrl.filter"
data="$ctrl.hours"> data="$ctrl.hours">
</vn-crud-model> </vn-crud-model>
<div ng-if="$ctrl.card.hasWorkCenter"> <div>
<vn-card class="vn-pa-lg vn-w-lg"> <vn-card class="vn-pa-lg vn-w-lg">
<vn-table model="model" auto-load="false"> <vn-table model="model" auto-load="false">
<vn-thead> <vn-thead>
@ -106,12 +106,6 @@
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
</div> </div>
<div
ng-if="!$ctrl.card.hasWorkCenter"
class="bg-title"
translate>
Autonomous worker
</div>
<vn-side-menu side="right"> <vn-side-menu side="right">
<div class="vn-pa-md"> <div class="vn-pa-md">

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "23.32.01", "version": "23.34.01",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "23.32.01", "version": "23.34.01",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",

View File

@ -539,9 +539,9 @@
<Stroke>2</Stroke> <Stroke>2</Stroke>
<Width>4</Width> <Width>4</Width>
</GraphicShape> </GraphicShape>
<GraphicShape xsi:type="WindowText" Style="Cross" IsPrint="true" PageAlignment="None" Locked="false" bStroke="true" bFill="true" Direction="Angle90" X="613" Y="24" Alignment="Left" AlignPointX="613" AlignPointY="24" FontScript="Default" FontCmd="Arial,118.2266,B&#xD;&#xA;" FontHeight="1" FontWidth="1" TextSpace="0" bSpaceCropping="false"> <GraphicShape xsi:type="WindowText" Style="Cross" IsPrint="true" PageAlignment="None" Locked="false" bStroke="true" bFill="true" Direction="Angle90" X="564" Y="32" Alignment="Left" AlignPointX="564" AlignPointY="32" FontScript="Default" FontCmd="Arial,118.2266,B&#xD;&#xA;" FontHeight="1" FontWidth="1" TextSpace="0" bSpaceCropping="false">
<qHitOnCircumferance>false</qHitOnCircumferance> <qHitOnCircumferance>false</qHitOnCircumferance>
<Selected>false</Selected> <Selected>true</Selected>
<iBackground_color>4294967295</iBackground_color> <iBackground_color>4294967295</iBackground_color>
<Id>5</Id> <Id>5</Id>
<ItemLabel>W5</ItemLabel> <ItemLabel>W5</ItemLabel>
@ -571,15 +571,15 @@
<BoundRectHeight>2896</BoundRectHeight> <BoundRectHeight>2896</BoundRectHeight>
<BoundRect> <BoundRect>
<Location> <Location>
<X>187</X> <X>138</X>
<Y>24</Y> <Y>32</Y>
</Location> </Location>
<Size> <Size>
<Width>426</Width> <Width>426</Width>
<Height>2896</Height> <Height>2896</Height>
</Size> </Size>
<X>187</X> <X>138</X>
<Y>24</Y> <Y>32</Y>
<Width>426</Width> <Width>426</Width>
<Height>2896</Height> <Height>2896</Height>
</BoundRect> </BoundRect>
@ -621,14 +621,14 @@
<Setup bInfinityPrint="false" LabelLength="610" LabelWidth="100" LeftMargin="0" TopMargin="0" LabelType="1" GapLength="3" FeedLength="0" ZSign="43" BlackMark="3" Position="0" Speed="2" Copy="1" bCopyDataBase="false" CopyField="None" Stripper="0" LabelsPerCut="0" DoubleCut_Enable="false" DoubleCut_OffsetLen="0" DoubleCut_FirstCutMode="1" Rotate180="200" Stop="35" Darkness="5" Number="1" bCutDataBase="false" bBatchCut="false" bNumberDataBase="false" NumberField="None" PageDirection="Portrait" PrintMode="0" PowerRFID="10" LengthRFID="0" RetryRFID="3" DrawMode="0"> <Setup bInfinityPrint="false" LabelLength="610" LabelWidth="100" LeftMargin="0" TopMargin="0" LabelType="1" GapLength="3" FeedLength="0" ZSign="43" BlackMark="3" Position="0" Speed="2" Copy="1" bCopyDataBase="false" CopyField="None" Stripper="0" LabelsPerCut="0" DoubleCut_Enable="false" DoubleCut_OffsetLen="0" DoubleCut_FirstCutMode="1" Rotate180="200" Stop="35" Darkness="5" Number="1" bCutDataBase="false" bBatchCut="false" bNumberDataBase="false" NumberField="None" PageDirection="Portrait" PrintMode="0" PowerRFID="10" LengthRFID="0" RetryRFID="3" DrawMode="0">
<Layout Shape="0" AcrossType="Copied" PageDirection="Portrait" HorAcross="1" VerAcross="1" HorGap="0" VerGap="0" HorAcrossMode1="1" VerAcrossMode1="1" LabelMode="0" HorGapMode1="0" VerGapMode1="0" BottomMargin="0" RightMargin="0" /> <Layout Shape="0" AcrossType="Copied" PageDirection="Portrait" HorAcross="1" VerAcross="1" HorGap="0" VerGap="0" HorAcrossMode1="1" VerAcrossMode1="1" LabelMode="0" HorGapMode1="0" VerGapMode1="0" BottomMargin="0" RightMargin="0" />
<Description>New label <Description>New label
Lang:(es-ES) OS:Microsoft Windows NT 10.0.22000.0(Win32NT)</Description> Lang:(es-ES) OS:Microsoft Windows NT 10.0.19045.0(Win32NT)</Description>
<UnitType>Mm</UnitType> <UnitType>Mm</UnitType>
<Dpi>203</Dpi> <Dpi>203</Dpi>
</Setup> </Setup>
<PrinterModel /> <PrinterModel />
<PrinterLanguage>EZPL</PrinterLanguage> <PrinterLanguage>EZPL</PrinterLanguage>
<USBName>GODEX G300#132207AB</USBName> <USBName>00000000</USBName>
<COMName>None</COMName> <COMName>COM1</COMName>
<CommunicationType>USB</CommunicationType> <CommunicationType>USB</CommunicationType>
<NetworkIPAddress>2886794855</NetworkIPAddress> <NetworkIPAddress>2886794855</NetworkIPAddress>
<NetworkPort>9100</NetworkPort> <NetworkPort>9100</NetworkPort>

View File

@ -1,9 +1,9 @@
[ [
{ {
"filename": "model.ezp", "filename": "model.ezpx",
"component": "printer-setup", "component": "printer-setup",
"path": "/assets/files/model.ezp", "path": "/assets/files/model.ezpx",
"cid": "model.ezp" "cid": "model.ezpx"
}, },
{ {
"filename": "port.png", "filename": "port.png",

View File

@ -0,0 +1,12 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/spacing.css`,
`${vnPrintPath}/common/css/misc.css`,
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/report.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,101 @@
html {
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
margin: 10px;
font-size: 22px;
}
.mainTable, .specialTable, .categoryTable {
width: 100%;
border-collapse: collapse;
font-size: inherit;
}
.mainTable td {
width: 50%;
border: 1px solid black;
vertical-align: top;
padding: 15px;
font-size: inherit;
}
.signTable {
height: 12%;
}
.signTable td {
width: calc(100% / 3);
border: 1px solid black;
vertical-align: top;
font-size: inherit;
padding: 15px;
border-top: none;
}
#title {
font-weight: bold;
font-size: 85px;
}
hr {
border: 1px solid #cccccc;
height: 0px;
border-radius: 25px;
}
#cellHeader {
border: 0px;
text-align: center;
vertical-align: middle;
}
#label, #merchandiseLabels {
font-size: 13px;
}
#merchandiseLabels {
border: none;
}
.imgSection {
text-align: center;
height: 200px;
overflow: hidden;
}
img {
object-fit: contain;
width: 100%;
height: 100%;
}
#lineBreak {
white-space: pre-line;
}
.specialTable td {
border: 1px solid black;
vertical-align: top;
padding: 15px;
font-size: inherit;
border-top: none;
border-bottom: none;
}
.specialTable #itemCategoryList {
width: 70%;
padding-top: 10px;
}
.categoryTable {
padding-bottom: none;
}
.categoryTable td {
vertical-align: top;
font-size: inherit;
border: none;
padding: 5px;
overflow: hidden;
}
.categoryTable #merchandiseLabels {
border-bottom: 4px solid #cccccc;
padding: none;
}
#merchandiseDetail {
font-weight: bold;
padding-top: 10px;
}
#merchandiseData {
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#merchandiseLabels td {
padding-bottom: 11px;
max-width: 300px;
}

View File

@ -0,0 +1,212 @@
<!DOCTYPE html>
<html>
<body>
<table class="mainTable">
<tr>
<td>
<span id="label">1. Remitente / Expediteur / Sender</span>
<hr>
<b>{{data.senderName}}</b><br>
{{data.senderStreet}}<br>
{{data.senderPostCode}} {{data.senderCity}} {{(data.senderCountry) ? `(${data.senderCountry})` : null}}
</td>
<td id="cellHeader">
<span id="title">CMR</span><br>
{{data.cmrFk}}
</td>
</tr>
<tr>
<td>
<span id="label">2. Consignatario / Destinataire / Consignee</span>
<hr>
<b>{{data.deliveryAddressFk}}<br>
{{data.deliveryName}}<br>
{{data.deliveryPhone || data.clientPhone}}
{{((data.deliveryPhone || data.clientPhone) && data.deliveryMobile) ? '/' : null}}
{{data.deliveryMobile}}</b>
</td>
<td>
<span id="label">16. Transportista / Transporteur / Carrier</span>
<hr>
<b>{{data.carrierName}}</b><br>
{{data.carrierStreet}}<br>
{{data.carrierPostalCode}} {{data.carrierCity}} {{(data.carrierCountry) ? `(${data.carrierCountry})` : null}}
</td>
</tr>
<tr>
<td>
<span id="label">
3. Lugar y fecha de entrega /
Lieu et date de livraison /
Place and date of delivery
</span>
<hr>
<b>{{data.deliveryStreet}}<br>
{{data.deliveryPostalCode}} {{data.deliveryCity}} {{(data.deliveryCountry) ? `(${data.deliveryCountry})` : null}}<br>
{{(data.ead) ? formatDate(data.ead, '%d/%m/%Y') : null}}</b>
</td>
<td>
<span id="label">17. Porteadores sucesivos / Transporteurs succesifs / Succesive Carriers</span>
<hr>
</td>
</tr>
<tr>
<td>
<span id="label">
4. Lugar y fecha de carga /
Lieu et date del prise en charge de la merchandise /
Place and date of taking over the goods
</span>
<hr>
<b>{{data.loadStreet}}<br>
{{data.loadPostalCode}} {{data.loadCity}} {{(data.loadCountry) ? `(${data.loadCountry})` : null}}<br>
{{formatDate(data.created, '%d/%m/%Y')}}</b>
</td>
<td rowspan="2">
<span id="label">
18. Obervaciones del transportista /
Reserves et observations du transporteur /
Carrier's reservations and observations
</span>
<hr>
<b>{{data.truckPlate}}</b><br>
{{data.observations}}
</td>
</tr>
<tr>
<td>
<span id="label">5. Documentos anexos / Documents annexes / Documents attached</span>
<hr>
</td>
</tr>
</table>
<table class="specialTable">
<tr>
<td>
<span id="label">
7 & 8. Número de bultos y clase de embalage /
Number of packages and packaging class /
Nombre de colis et classe d'emballage
</span>
<hr>
<div id="lineBreak">
<b>{{data.packagesList}}</b>
</div>
</td>
<td id="itemCategoryList">
<table class="categoryTable">
<tr id="merchandiseLabels">
<td>6. Marcas y números / Brands and numbers / Marques et numéros</td>
<td>9. Naturaleza de la merc. / Nature of goods / Nature des marchandises</td>
<td>10. nº Estadístico / Statistical no. / n° statistique</td>
<td>11. Peso bruto / Gross weight / Poids brut (kg)</td>
<td>12. Volumen / Volume (m3)</td>
</tr>
<tr v-for="merchandise in merchandises" id="merchandiseData">
<td>{{merchandise.ticketFk}}</td>
<td>{{merchandise.name}}</td>
<td>N/A</td>
<td>{{merchandise.weight}}</td>
<td>{{merchandise.volume}}</td>
</tr>
</table>
<div v-if="!merchandises" id="merchandiseDetail">
{{data.merchandiseDetail}}
</div>
</td>
</tr>
</table>
<table class="mainTable">
<tr>
<td>
<span id="label">
13. Instrucciones del remitente /
Instrunstions de l'expèditeur / Sender
instruccions
</span>
<hr>
<b>{{data.senderInstruccions}}</b>
</td>
<td>
<span id="label">
19. Estipulaciones particulares /
Conventions particulieres /
Special agreements
</span>
<hr>
<b>{{data.specialAgreements}}</b>
</td>
</tr>
<tr>
<td>
<span id="label">
14. Forma de pago /
Prescriptions d'affranchissement /
Instruction as to payment for carriage
</span>
<hr>
<b>{{data.paymentInstruccions}}</b>
</td>
<td>
<span id="label">20. A pagar por / Être payé pour / To be paid by</span>
<hr>
</td>
</tr>
<tr>
<td>
<span id="label">21. Formalizado en / Etabile a / Estabilshed in</span>
<hr>
<b>{{data.loadStreet}}</b><br>
{{data.loadPostalCode}} {{data.loadCity}} {{(data.loadCountry) ? `(${data.loadCountry})` : null}} <br>
</td>
<td>
<span id="label">15. Reembolso / Remboursement / Cash on delivery</span>
<hr>
</td>
</tr>
</table>
<table class="signTable">
<tr>
<td>
<span id="label">
22. Firma y sello del remitente /
Signature et timbre de l'expèditeur /
Signature and stamp of the sender
</span>
<hr>
<div class="imgSection">
<img :src="senderStamp"/>
</div>
</td>
<td>
<span id="label">
23. Firma y sello del transportista /
Signature et timbre du transporteur /
Signature and stamp of the carrier
</span>
<hr>
<div class="imgSection">
<img :src="deliveryStamp"/>
</div>
</td>
<td>
<span id="label">
24. Firma y sello del consignatario /
Signature et timbre du destinataire /
Signature and stamp of the consignee
</span>
<hr>
<div class="imgSection">
<img :src="signPath"/>
</div>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,45 @@
const config = require(`vn-print/core/config`);
const vnReport = require('../../../core/mixins/vn-report.js');
const md5 = require('md5');
const fs = require('fs-extra');
const prefixBase64 = 'data:image/png;base64,';
module.exports = {
name: 'cmr',
mixins: [vnReport],
async serverPrefetch() {
this.data = await this.findOneFromDef('data', [this.id]);
if (this.data.ticketFk) {
this.merchandises = await this.rawSqlFromDef('merchandise', [this.data.ticketFk]);
this.signature = await this.findOneFromDef('signature', [this.data.ticketFk]);
} else
this.merchandises = null;
this.senderStamp = (this.data.senderStamp)
? `${prefixBase64} ${this.data.senderStamp.toString('base64')}`
: null;
this.deliveryStamp = (this.data.deliveryStamp)
? `${prefixBase64} ${this.data.deliveryStamp.toString('base64')}`
: null;
},
props: {
id: {
type: Number,
required: true,
description: 'The cmr id'
},
},
computed: {
signPath() {
if (!this.signature) return;
const signatureName = this.signature.signature
const hash = md5(signatureName.toString()).substring(0, 3);
const file = `${config.storage.root}/${hash}/${signatureName}.png`;
if (!fs.existsSync(file)) return null;
return `${prefixBase64} ${Buffer.from(fs.readFileSync(file), 'utf8').toString('base64')}`;
},
}
};

View File

@ -0,0 +1 @@
reportName: cmr

View File

@ -0,0 +1,3 @@
{
"format": "A4"
}

View File

@ -0,0 +1,52 @@
SELECT c.id cmrFk,
t.id ticketFk,
c.truckPlate,
c.observations,
c.senderInstruccions,
c.paymentInstruccions,
c.specialAgreements,
c.created,
c.packagesList,
c.merchandiseDetail,
c.ead,
s.name carrierName,
s.street carrierStreet,
s.postCode carrierPostCode,
s.city carrierCity,
cou.country carrierCountry,
s2.name senderName,
s2.street senderStreet,
s2.postCode senderPostCode,
s2.city senderCity,
cou2.country senderCountry,
a.street deliveryStreet,
a.id deliveryAddressFk,
a.postalCode deliveryPostalCode,
a.city deliveryCity,
a.nickname deliveryName,
a.phone deliveryPhone,
a.mobile deliveryMobile,
cou3.country deliveryCountry,
cl.phone clientPhone,
a2.street loadStreet,
a2.postalCode loadPostalCode,
a2.city loadCity,
cou4.country loadCountry,
co.stamp senderStamp,
s.stamp deliveryStamp
FROM cmr c
LEFT JOIN supplier s ON s.id = c.supplierFk
LEFT JOIN country cou ON cou.id = s.countryFk
LEFT JOIN company co ON co.id = c.companyFk
LEFT JOIN supplierAccount sa ON sa.id = co.supplierAccountFk
LEFT JOIN supplier s2 ON s2.id = sa.supplierFk
LEFT JOIN country cou2 ON cou2.id = s2.countryFk
LEFT JOIN `address` a ON a.id = c.addressToFk
LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN country cou3 ON cou3.id = p.countryFk
LEFT JOIN client cl ON cl.id = a.clientFk
LEFT JOIN `address` a2 ON a2.id = c.addressFromFk
LEFT JOIN province p2 ON p2.id = a2.provinceFk
LEFT JOIN country cou4 ON cou4.id = p2.countryFk
LEFT JOIN ticket t ON t.cmrFk = c.id
WHERE c.id = ?

View File

@ -0,0 +1,11 @@
SELECT s.ticketFk,
ic.name,
CAST(SUM(sv.weight) AS DECIMAL(10,2)) `weight`,
CAST(SUM(sv.volume) AS DECIMAL(10,3)) volume
FROM sale s
JOIN saleVolume sv ON sv.saleFk = s.id
JOIN item i ON i.id = s.itemFk
JOIN itemType it ON it.id = i.typeFk
JOIN itemCategory ic ON ic.id = it.categoryFk
WHERE sv.ticketFk = ?
GROUP BY ic.id

View File

@ -0,0 +1,5 @@
SELECT dc.id `signature`
FROM ticket t
JOIN ticketDms dt ON dt.ticketFk = t.id
LEFT JOIN dms dc ON dc.id = dt.dmsFk
WHERE t.id = ?