Merge branch 'dev' into 3261-ticket_request
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2021-11-15 09:14:55 +00:00
commit fac1122438
63 changed files with 3155 additions and 1928 deletions

View File

@ -25,6 +25,9 @@
},
"isAutoConciliated": {
"type": "boolean"
},
"maxAmount": {
"type": "number"
}
},
"acls": [{

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.accountingType ADD maxAmount INT DEFAULT NULL NULL;
UPDATE vn.accountingType SET maxAmount = 1000 WHERE code = 'cash';

View File

@ -0,0 +1,4 @@
ALTER TABLE vn.payMethod CHANGE ibanRequired ibanRequiredForClients tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod ADD ibanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod CHANGE ibanRequiredForSuppliers ibanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL AFTER ibanRequiredForClients;
UPDATE vn.payMethod SET ibanRequiredForSuppliers = 1 WHERE code = 'wireTransfer';

View File

@ -34,7 +34,7 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-10-20 8:38:20
-- Dump completed on 2021-11-08 8:19:54
USE `account`;
-- MariaDB dump 10.19 Distrib 10.6.4-MariaDB, for Linux (x86_64)
--
@ -120,7 +120,7 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-10-20 8:38:22
-- Dump completed on 2021-11-08 8:19:55
USE `salix`;
-- MariaDB dump 10.19 Distrib 10.6.4-MariaDB, for Linux (x86_64)
--
@ -176,7 +176,7 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-10-20 8:38:23
-- Dump completed on 2021-11-08 8:19:56
USE `vn`;
-- MariaDB dump 10.19 Distrib 10.6.4-MariaDB, for Linux (x86_64)
--
@ -432,7 +432,7 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-10-20 8:38:27
-- Dump completed on 2021-11-08 8:20:01
USE `cache`;
-- MariaDB dump 10.19 Distrib 10.6.4-MariaDB, for Linux (x86_64)
--
@ -468,7 +468,7 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-10-20 8:38:28
-- Dump completed on 2021-11-08 8:20:02
USE `hedera`;
-- MariaDB dump 10.19 Distrib 10.6.4-MariaDB, for Linux (x86_64)
--
@ -534,7 +534,7 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-10-20 8:38:29
-- Dump completed on 2021-11-08 8:20:03
USE `postgresql`;
-- MariaDB dump 10.19 Distrib 10.6.4-MariaDB, for Linux (x86_64)
--
@ -608,7 +608,7 @@ UNLOCK TABLES;
LOCK TABLES `workcenter` WRITE;
/*!40000 ALTER TABLE `workcenter` DISABLE KEYS */;
INSERT INTO `workcenter` VALUES (1,'Silla',20,NULL,1,'Av espioca 100',552703),(2,'Mercaflor',19,NULL,NULL,NULL,NULL),(3,'Marjales',26,20008,NULL,NULL,NULL),(4,'VNH',NULL,NULL,3,NULL,NULL),(5,'Madrid',28,2867,5,'Av constitución 3',554145),(6,'Vilassar',88,88038,2,'Cami del Crist, 33',556412),(7,'Tenerife',NULL,NULL,10,NULL,NULL),(8,NULL,NULL,NULL,NULL,NULL,NULL),(9,'Algemesi',20,1348,60,'Fenollars, 2',523549),(10,NULL,NULL,NULL,NULL,NULL,NULL);
INSERT INTO `workcenter` VALUES (1,'Silla',20,NULL,1,'Av espioca 100',552703),(2,'Mercaflor',19,NULL,NULL,NULL,NULL),(3,'Marjales',26,20008,NULL,NULL,NULL),(4,'VNH',NULL,NULL,3,NULL,NULL),(5,'Madrid',28,2868,5,'Av constitución 3',554145),(6,'Vilassar',88,88038,2,'Cami del Crist, 33',556412),(7,'Tenerife',NULL,NULL,10,NULL,NULL),(8,NULL,NULL,NULL,NULL,NULL,NULL),(9,'Algemesi',20,1351,60,'Fenollars, 2',523549),(10,NULL,NULL,NULL,NULL,NULL,NULL);
/*!40000 ALTER TABLE `workcenter` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
@ -620,7 +620,7 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-10-20 8:38:30
-- Dump completed on 2021-11-08 8:20:05
USE `sage`;
-- MariaDB dump 10.19 Distrib 10.6.4-MariaDB, for Linux (x86_64)
--
@ -676,4 +676,4 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2021-10-20 8:38:31
-- Dump completed on 2021-11-08 8:20:06

View File

@ -154,16 +154,16 @@ INSERT INTO `vn`.`shelving` (`code`, `parkingFk`, `isPrinted`, `priority`, `park
('GVC', '1', '0', '1', '0', '1106'),
('HEJ', '2', '0', '1', '0', '1106');
INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`,`code`)
INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`,`code`, `maxAmount`)
VALUES
(1, 'CC y Polizas de crédito', NULL, NULL),
(2, 'Cash', 'Cash', 'cash'),
(3, 'Credit card', 'Credit Card', 'creditCard'),
(4, 'Finalcial lines', NULL, NULL),
(5, 'Other products', NULL, NULL),
(6, 'Loans', NULL, NULL),
(7, 'Leasing', NULL, NULL),
(8, 'Compensations', 'Compensations', 'compensation');
(1, 'CC y Polizas de crédito', NULL, NULL, NULL),
(2, 'Cash', 'Cash', 'cash', 1000),
(3, 'Credit card', 'Credit Card', 'creditCard', NULL),
(4, 'Finalcial lines', NULL, NULL, NULL),
(5, 'Other products', NULL, NULL, NULL),
(6, 'Loans', NULL, NULL, NULL),
(7, 'Leasing', NULL, NULL, NULL),
(8, 'Compensations', 'Compensations', 'compensation', NULL);
INSERT INTO `vn`.`bank`(`id`, `bank`, `account`, `cash`, `entityFk`, `isActive`, `currencyFk`)
VALUES
@ -217,14 +217,14 @@ UPDATE `vn`.`agencyMode` SET `web` = 1, `reportMail` = 'no-reply@gothamcity.com'
UPDATE `vn`.`agencyMode` SET `code` = 'refund' WHERE `id` = 23;
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `ibanRequired`)
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `ibanRequiredForClients`, `ibanRequiredForSuppliers`)
VALUES
(1, NULL, 'PayMethod one', 0, 001, 0),
(2, NULL, 'PayMethod two', 10, 001, 0),
(3, 'compensation', 'PayMethod three', 0, 001, 0),
(4, NULL, 'PayMethod with IBAN', 0, 001, 1),
(5, NULL, 'PayMethod five', 10, 001, 0),
(8,'wireTransfer', 'WireTransfer', 5, 001, 1);
(1, NULL, 'PayMethod one', 0, 001, 0, 0),
(2, NULL, 'PayMethod two', 10, 001, 0, 0),
(3, 'compensation', 'PayMethod three', 0, 001, 0, 0),
(4, NULL, 'PayMethod with IBAN', 0, 001, 1, 0),
(5, NULL, 'PayMethod five', 10, 001, 0, 0),
(8,'wireTransfer', 'WireTransfer', 5, 001, 1, 1);
INSERT INTO `vn`.`payDem`(`id`, `payDem`)
VALUES
@ -1285,11 +1285,11 @@ INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `pr
(5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'),
(6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222');
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`)
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`)
VALUES
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants'),
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals'),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'flowerPlants');
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1),
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'flowerPlants', 1);
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
/* eslint max-len: ["error", { "ignoreStrings": true }]*/
export default {
globalItems: {
@ -273,6 +274,7 @@ export default {
deliveredAmount: '.vn-dialog vn-input-number[ng-model="$ctrl.deliveredAmount"]',
refundAmount: '.vn-dialog vn-input-number[ng-model="$ctrl.amountToReturn"]',
saveButton: '.vn-dialog.shown [response="accept"]',
anyBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr',
firstLineBalance: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)',
firstLineReference: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable',
firstLineReferenceInput: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable > div > field > vn-textfield'
@ -470,6 +472,7 @@ export default {
firstTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1) > vn-check',
secondTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(2) > vn-td:nth-child(1) > vn-check',
thirdTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(3) > vn-td:nth-child(1) > vn-check',
fifthTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(5) > vn-td:nth-child(1) > vn-check',
sixthTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(6) > vn-td:nth-child(1) > vn-check',
payoutButton: 'vn-ticket-index vn-button[icon="icon-recovery"]',
payoutCompany: '.vn-dialog vn-autocomplete[ng-model="$ctrl.companyFk"]',
@ -1144,6 +1147,7 @@ export default {
alias: 'vn-supplier-basic-data vn-textfield[ng-model="$ctrl.supplier.nickname"]',
isSerious: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isSerious"]',
isActive: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isActive"]',
isPayMethodChecked: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isPayMethodChecked"]',
notes: 'vn-supplier-basic-data vn-textarea[ng-model="$ctrl.supplier.note"]',
saveButton: 'vn-supplier-basic-data button[type="submit"]',
},

View File

@ -105,7 +105,23 @@ describe('Client balance path', () => {
expect(result).toContain('-€100.00');
});
it('should create a new payment and check the cash exceeded the maximum', async() => {
const amountPaid = '1001';
await page.closePopup();
await page.waitToClick(selectors.clientBalance.newPaymentButton);
await page.autocompleteSearch(selectors.clientBalance.newPaymentBank, 'Cash');
await page.write(selectors.clientBalance.newPaymentAmount, amountPaid);
await page.clearInput(selectors.clientBalance.newDescription);
await page.write(selectors.clientBalance.newDescription, 'Payment');
await page.waitToClick(selectors.clientBalance.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Amount exceeded');
});
it('should create a new payment that sets the balance back to the original negative value', async() => {
await page.closePopup();
await page.waitToClick(selectors.clientBalance.newPaymentButton);
await page.autocompleteSearch(selectors.clientBalance.newPaymentBank, 'Pay on receipt');
await page.overwrite(selectors.clientBalance.newPaymentAmount, '-150');

View File

@ -21,8 +21,8 @@ describe('Ticket index payout path', () => {
it('should check the second ticket from a client and 1 of another', async() => {
await page.waitToClick(selectors.globalItems.searchButton);
await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.thirdTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.fifthTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.payoutButton);
const message = await page.waitForSnackbar();
@ -58,9 +58,11 @@ describe('Ticket index payout path', () => {
await page.selectModule('client');
await page.accessToSearchResult('1101');
await page.accessToSection('client.card.balance.index');
await page.waitForSelector('vn-client-balance-index vn-tbody > vn-tr');
let result = await page.countElement('vn-client-balance-index vn-tbody > vn-tr');
await page.waitForSelector(selectors.clientBalance.anyBalanceLine);
const count = await page.countElement(selectors.clientBalance.anyBalanceLine);
const reference = await page.waitToGetProperty(selectors.clientBalance.firstLineReference, 'innerText');
expect(result).toEqual(4);
expect(count).toEqual(4);
expect(reference).toContain('Cash, Albaran: 7, 8Payment');
});
});

View File

@ -22,6 +22,7 @@ describe('Supplier basic data path', () => {
await page.write(selectors.supplierBasicData.alias, 'Plants Nick SL');
await page.waitToClick(selectors.supplierBasicData.isSerious);
await page.waitToClick(selectors.supplierBasicData.isActive);
await page.waitToClick(selectors.supplierBasicData.isPayMethodChecked);
await page.write(selectors.supplierBasicData.notes, 'Some notes');
await page.waitToClick(selectors.supplierBasicData.saveButton);
@ -52,6 +53,12 @@ describe('Supplier basic data path', () => {
expect(result).toBe('unchecked');
});
it('should check the isPayMethodChecked checkbox is now unchecked', async() => {
const result = await page.checkboxState(selectors.supplierBasicData.isPayMethodChecked);
expect(result).toBe('unchecked');
});
it('should check the notes were edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierBasicData.notes, 'value');

View File

@ -28,7 +28,7 @@
url="Workers/activeWithRole"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'salesPerson'}"
where="{role: {inq: ['salesPerson', 'officeBoss']}}"
label="Salesperson">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>

View File

@ -129,7 +129,7 @@ module.exports = Self => {
function hasIban(err, done) {
Self.app.models.PayMethod.findById(this.payMethodFk, (_, instance) => {
if (instance && instance.ibanRequired && !this.iban)
if (instance && instance.ibanRequiredForClients && !this.iban)
err();
done();
});

View File

@ -25,7 +25,10 @@
"outstandingDebt": {
"type": "Number"
},
"ibanRequired": {
"ibanRequiredForClients": {
"type": "boolean"
},
"ibanRequiredForSuppliers": {
"type": "boolean"
}
}

View File

@ -44,7 +44,8 @@
label="Amount"
ng-model="$ctrl.amountPaid"
step="0.01"
required="true">
required="true"
max="$ctrl.maxAmount">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>

View File

@ -45,6 +45,7 @@ class Controller extends Dialog {
set description(value) {
this.receipt.description = value;
this.originalDescription = value;
}
get description() {
@ -60,7 +61,14 @@ class Controller extends Dialog {
if (value) {
const accountingType = value.accountingType;
this.receipt.description = accountingType && accountingType.receiptDescription;
if (this.originalDescription) {
this.receipt.description =
`${accountingType && accountingType.receiptDescription}, ${this.originalDescription}`;
} else {
this.receipt.description =
`${accountingType && accountingType.receiptDescription}`;
}
this.maxAmount = accountingType && accountingType.maxAmount;
}
}
@ -121,6 +129,11 @@ class Controller extends Dialog {
if (response !== 'accept')
return super.responseHandler(response);
const exceededAmount = this.receipt.amountPaid > this.maxAmount;
if (this.bankSelection.accountingType.code == 'cash' && exceededAmount)
return this.vnApp.showError(this.$t('Amount exceeded', {maxAmount: this.maxAmount}));
let receiptId;
return this.$http.post(`Clients/${this.clientFk}/createReceipt`, this.receipt)
.then(res => {

View File

@ -23,10 +23,12 @@ describe('Client', () => {
clientFk: 1101,
companyFk: 442
};
controller.bankSelection = {accountingType: {code: 'myCode'}};
}));
describe('bankSelection() setter', () => {
it('should set the receipt description property', () => {
controller.originalDescription = 'Albaran: 1, 2';
controller.bankSelection = {
id: 1,
bank: 'Cash',
@ -36,7 +38,7 @@ describe('Client', () => {
}
};
expect(controller.receipt.description).toEqual('Cash');
expect(controller.receipt.description).toEqual('Cash, Albaran: 1, 2');
});
});

View File

@ -1 +1,2 @@
View receipt: Ver recibo
Amount exceeded: Según ley contra el fraude no se puede recibir cobros por importe igual o superior a {{maxAmount}}

View File

@ -19,7 +19,7 @@
vn-acl="salesAssistant"
ng-model="$ctrl.client.payMethodFk"
data="paymethods"
fields="['ibanRequired']"
fields="['ibanRequiredForClients']"
initial-data="$ctrl.client.payMethod">
</vn-autocomplete>
<vn-input-number

View File

@ -34,13 +34,20 @@
</div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link
tooltip="Supplier card"
state="['supplier.card.summary', {id: $ctrl.entry.supplier.id}]"
icon="icon-supplier">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<vn-quick-link
tooltip="All travels with current agency"
state="['travel.index', {q: $ctrl.travelFilter}]"
icon="local_airport">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<div ng-transclude="btnThree">
<vn-quick-link
tooltip="All entries with current supplier"
state="['entry.index', {q: $ctrl.entryFilter}]"

View File

@ -1,4 +1,5 @@
Reference: Referencia
Supplier card: Ficha del proveedor
All travels with current agency: Todos los envios con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual
Show entry report: Ver informe del pedido

View File

@ -56,7 +56,7 @@ module.exports = Self => {
{
relation: 'client',
scope: {
fields: ['id', 'socialName']
fields: ['id', 'socialName', 'email']
}
}
]

View File

@ -0,0 +1,139 @@
<vn-icon-button
icon="more_vert"
vn-popover="menu">
</vn-icon-button>
<vn-menu vn-id="menu">
<vn-list>
<vn-item class="dropdown"
vn-click-stop="showInvoiceMenu.show($event, 'left')"
name="showInvoicePdf"
translate>
Show invoice...
<vn-menu vn-id="showInvoiceMenu">
<vn-list>
<a class="vn-item"
href="api/InvoiceOuts/{{$ctrl.id}}/download?access_token={{$ctrl.vnToken.token}}"
target="_blank"
name="showInvoicePdf"
translate>
Show as PDF
</a>
<vn-item
ng-click="$ctrl.showCsvInvoice()"
translate>
Show as CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item class="dropdown"
vn-click-stop="sendInvoiceMenu.show($event, 'left')"
name="sendInvoice"
translate>
Send invoice...
<vn-menu vn-id="sendInvoiceMenu">
<vn-list>
<vn-item
ng-click="sendPdfConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send PDF
</vn-item>
<vn-item
ng-click="sendCsvConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item
ng-click="deleteConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="deleteInvoice"
translate>
Delete Invoice
</vn-item>
<vn-item
ng-click="bookConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="bookInvoice"
translate>
Book invoice
</vn-item>
<vn-item
ng-click="createInvoicePdfConfirmation.show()"
ng-show="$ctrl.hasInvoicing || !$ctrl.invoiceOut.hasPdf"
name="regenerateInvoice"
translate>
{{!$ctrl.invoiceOut.hasPdf ? 'Generate PDF invoice': 'Regenerate PDF invoice'}}
</vn-item>
<vn-item
ng-click="$ctrl.showExportationLetter()"
ng-show="$ctrl.invoiceOut.serial == 'E'"
translate>
Show CIES letter
</vn-item>
</vn-list>
</vn-menu>
<vn-confirm
vn-id="deleteConfirmation"
on-accept="$ctrl.deleteInvoiceOut()"
question="Are you sure you want to delete this invoice?">
</vn-confirm>
<vn-confirm
vn-id="bookConfirmation"
on-accept="$ctrl.bookInvoiceOut()"
question="Are you sure you want to book this invoice?">
</vn-confirm>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<!-- Create invoice PDF confirmation dialog -->
<vn-confirm
vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.createPdfInvoice()"
question="Are you sure you want to generate/regenerate the PDF invoice?"
message="Generate PDF invoice document">
</vn-confirm>
<!-- Send PDF invoice confirmation popup -->
<vn-dialog
vn-id="sendPdfConfirmation"
on-accept="$ctrl.sendPdfInvoice($data)"
message="Send PDF invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
label="Email"
ng-model="sendPdfConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
<!-- Send CSV invoice confirmation popup -->
<vn-dialog
vn-id="sendCsvConfirmation"
on-accept="$ctrl.sendCsvInvoice($data)"
message="Send CSV invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
label="Email"
ng-model="sendCsvConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>

View File

@ -0,0 +1,121 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $, vnReport, vnEmail) {
super($element, $);
this.vnReport = vnReport;
this.vnEmail = vnEmail;
}
get invoiceOut() {
return this._invoiceOut;
}
set invoiceOut(value) {
this._invoiceOut = value;
if (value)
this.id = value.id;
}
loadData() {
const filter = {
include: [
{
relation: 'company',
scope: {
fields: ['id', 'code']
}
}, {
relation: 'client',
scope: {
fields: ['id', 'name', 'email']
}
}
]
};
return this.$http.get(`InvoiceOuts/${this.invoiceOut.id}`, {filter})
.then(res => this.invoiceOut = res.data);
}
reload() {
return this.loadData().then(() => {
if (this.parentReload)
this.parentReload();
});
}
cardReload() {
// Prevents error when not defined
}
deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.id}/delete`)
.then(() => this.$state.go('invoiceOut.index'))
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted')));
}
bookInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`)
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
}
createPdfInvoice() {
return this.$http.post(`InvoiceOuts/${this.id}/createPdf`)
.then(() => this.reload())
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);
this.vnApp.showSuccess(snackbarMessage);
});
}
showCsvInvoice() {
this.vnReport.showCsv('invoice', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id
});
}
sendPdfInvoice($data) {
if (!$data.email)
return this.vnApp.showError(this.$t(`The email can't be empty`));
return this.vnEmail.send('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
sendCsvInvoice($data) {
if (!$data.email)
return this.vnApp.showError(this.$t(`The email can't be empty`));
return this.vnEmail.sendCsv('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
showExportationLetter() {
this.vnReport.show('exportation', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id
});
}
}
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];
ngModule.vnComponent('vnInvoiceOutDescriptorMenu', {
template: require('./index.html'),
controller: Controller,
bindings: {
invoiceOut: '<',
parentReload: '&'
}
});

View File

@ -0,0 +1,96 @@
import './index';
describe('vnInvoiceOutDescriptorMenu', () => {
let controller;
let $httpBackend;
let $httpParamSerializer;
const invoiceOut = {
id: 1,
client: {id: 1101}
};
beforeEach(ngModule('invoiceOut'));
beforeEach(inject(($componentController, _$httpParamSerializer_, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnInvoiceOutDescriptorMenu', {$element: null});
}));
describe('createPdfInvoice()', () => {
it('should make a query to the createPdf() endpoint and show a success snackbar', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.invoiceOut = invoiceOut;
$httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond();
controller.createPdfInvoice();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('showCsvInvoice()', () => {
it('should make a query to the csv invoice download endpoint and show a message snackbar', () => {
jest.spyOn(window, 'open').mockReturnThis();
controller.invoiceOut = invoiceOut;
const expectedParams = {
invoiceId: invoiceOut.id,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/csv/invoice/download?${serializedParams}`;
controller.showCsvInvoice();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
describe('sendPdfInvoice()', () => {
it('should make a query to the email invoice endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`email/invoice?${serializedParams}`).respond();
controller.sendPdfInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
describe('sendCsvInvoice()', () => {
it('should make a query to the csv invoice send endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`csv/invoice/send?${serializedParams}`).respond();
controller.sendCsvInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,17 @@
Show invoice...: Ver factura...
Send invoice...: Enviar factura...
Send PDF invoice: Enviar factura en PDF
Send CSV invoice: Enviar factura en CSV
Delete Invoice: Eliminar factura
Clone Invoice: Clonar factura
Book invoice: Asentar factura
Generate PDF invoice: Generar PDF factura
Show CIES letter: Ver carta CIES
InvoiceOut deleted: Factura eliminada
Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura?
Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura?
InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
The email can't be empty: El correo no puede estar vacío

View File

@ -0,0 +1,24 @@
@import "./effects";
@import "variables";
vn-invoice-out-descriptor-menu {
& > vn-icon-button[icon="more_vert"] {
display: flex;
min-width: 45px;
height: 45px;
box-sizing: border-box;
align-items: center;
justify-content: center;
}
& > vn-icon-button[icon="more_vert"] {
@extend %clickable;
color: inherit;
& > vn-icon {
padding: 10px;
}
vn-icon {
font-size: 1.75rem;
}
}
}

View File

@ -1,81 +1,12 @@
<vn-descriptor-content
module="invoiceOut"
description="$ctrl.invoiceOut.ref">
<slot-menu>
<vn-item class="dropdown"
vn-click-stop="showInvoiceMenu.show($event, 'left')"
name="showInvoicePdf"
translate>
Show invoice...
<vn-menu vn-id="showInvoiceMenu">
<vn-list>
<a class="vn-item"
href="api/InvoiceOuts/{{$ctrl.id}}/download?access_token={{$ctrl.vnToken.token}}"
target="_blank"
name="showInvoicePdf"
translate>
Show as PDF
</a>
<vn-item
ng-click="$ctrl.showCsvInvoice()"
translate>
Show as CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item class="dropdown"
vn-click-stop="sendInvoiceMenu.show($event, 'left')"
name="sendInvoice"
translate>
Send invoice...
<vn-menu vn-id="sendInvoiceMenu">
<vn-list>
<vn-item
ng-click="sendPdfConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send PDF
</vn-item>
<vn-item
ng-click="sendCsvConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item
ng-click="deleteConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="deleteInvoice"
translate>
Delete Invoice
</vn-item>
<vn-item
ng-click="bookConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="bookInvoice"
translate>
Book invoice
</vn-item>
<vn-item
ng-click="createInvoicePdfConfirmation.show()"
ng-show="$ctrl.hasInvoicing || !$ctrl.invoiceOut.hasPdf"
name="regenerateInvoice"
translate>
{{!$ctrl.invoiceOut.hasPdf ? 'Generate PDF invoice': 'Regenerate PDF invoice'}}
</vn-item>
<vn-item
ng-click="$ctrl.showExportationLetter()"
ng-show="$ctrl.invoiceOut.serial == 'E'"
translate>
Show CIES letter
</vn-item>
</slot-menu>
<slot-dot-menu>
<vn-invoice-out-descriptor-menu
invoice-out="$ctrl.invoiceOut"
parent-reload="$ctrl.reload()"
/>
</slot-dot-menu>
<slot-body>
<div class="attributes">
<vn-label-value
@ -119,58 +50,3 @@
</div>
</slot-body>
</vn-descriptor-content>
<vn-confirm
vn-id="deleteConfirmation"
on-accept="$ctrl.deleteInvoiceOut()"
question="Are you sure you want to delete this invoice?">
</vn-confirm>
<vn-confirm
vn-id="bookConfirmation"
on-accept="$ctrl.bookInvoiceOut()"
question="Are you sure you want to book this invoice?">
</vn-confirm>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<!-- Create invoice PDF confirmation dialog -->
<vn-confirm
vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.createPdfInvoice()"
question="Are you sure you want to generate/regenerate the PDF invoice?"
message="Generate PDF invoice document">
</vn-confirm>
<!-- Send PDF invoice confirmation popup -->
<vn-dialog
vn-id="sendPdfConfirmation"
on-accept="$ctrl.sendPdfInvoice($data)"
message="Send PDF invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
ng-model="sendPdfConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
<!-- Send CSV invoice confirmation popup -->
<vn-dialog
vn-id="sendCsvConfirmation"
on-accept="$ctrl.sendCsvInvoice($data)"
message="Send CSV invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
ng-model="sendCsvConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>

View File

@ -41,70 +41,6 @@ class Controller extends Descriptor {
return this.getData(`InvoiceOuts/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
reload() {
return this.loadData().then(() => {
if (this.cardReload)
this.cardReload();
});
}
cardReload() {
// Prevents error when not defined
}
deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.id}/delete`)
.then(() => this.$state.go('invoiceOut.index'))
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted')));
}
bookInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`)
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
}
createPdfInvoice() {
const invoiceId = this.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => this.reload())
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);
this.vnApp.showSuccess(snackbarMessage);
});
}
showCsvInvoice() {
this.vnReport.showCsv('invoice', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id,
});
}
sendPdfInvoice($data) {
return this.vnEmail.send('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
sendCsvInvoice($data) {
return this.vnEmail.sendCsv('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
showExportationLetter() {
this.vnReport.show('exportation', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id,
});
}
}
ngModule.vnComponent('vnInvoiceOutDescriptor', {
@ -112,6 +48,5 @@ ngModule.vnComponent('vnInvoiceOutDescriptor', {
controller: Controller,
bindings: {
invoiceOut: '<',
cardReload: '&'
}
});

View File

@ -3,17 +3,11 @@ import './index';
describe('vnInvoiceOutDescriptor', () => {
let controller;
let $httpBackend;
let $httpParamSerializer;
const invoiceOut = {
id: 1,
client: {id: 1101}
};
beforeEach(ngModule('invoiceOut'));
beforeEach(inject(($componentController, _$httpParamSerializer_, _$httpBackend_) => {
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnInvoiceOutDescriptor', {$element: null});
}));
@ -29,81 +23,4 @@ describe('vnInvoiceOutDescriptor', () => {
expect(controller.invoiceOut).toEqual(response);
});
});
describe('createPdfInvoice()', () => {
it('should make a query to the createPdf() endpoint and show a success snackbar', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.invoiceOut = invoiceOut;
$httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond();
controller.createPdfInvoice();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('showCsvInvoice()', () => {
it('should make a query to the csv invoice download endpoint and show a message snackbar', () => {
jest.spyOn(window, 'open').mockReturnThis();
controller.invoiceOut = invoiceOut;
const expectedParams = {
invoiceId: invoiceOut.id,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/csv/invoice/download?${serializedParams}`;
controller.showCsvInvoice();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
describe('sendPdfInvoice()', () => {
it('should make a query to the email invoice endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`email/invoice?${serializedParams}`).respond();
controller.sendPdfInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
describe('sendCsvInvoice()', () => {
it('should make a query to the csv invoice send endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`csv/invoice/send?${serializedParams}`).respond();
controller.sendCsvInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
});

View File

@ -1,20 +1,2 @@
Volume exceded: Volumen excedido
Volume: Volumen
Client card: Ficha del cliente
Invoice ticket list: Listado de tickets de la factura
Show invoice...: Ver factura...
Send invoice...: Enviar factura...
Send PDF invoice: Enviar factura en PDF
Send CSV invoice: Enviar factura en CSV
Delete Invoice: Eliminar factura
Clone Invoice: Clonar factura
Book invoice: Asentar factura
Generate PDF invoice: Generar PDF factura
Show CIES letter: Ver carta CIES
InvoiceOut deleted: Factura eliminada
Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura?
Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura?
InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado

View File

@ -7,5 +7,6 @@ import './summary';
import './card';
import './descriptor';
import './descriptor-popover';
import './descriptor-menu';
import './index/manual';
import './index/global-invoicing';

View File

@ -13,6 +13,10 @@
<vn-icon-button icon="launch"></vn-icon-button>
</a>
<span>{{$ctrl.summary.invoiceOut.ref}} - {{$ctrl.summary.invoiceOut.client.socialName}}</span>
<vn-invoice-out-descriptor-menu
invoice-out="$ctrl.summary.invoiceOut"
parent-reload="$ctrl.reload()"
/>
</h5>
<vn-horizontal>
<vn-one>

View File

@ -6,7 +6,7 @@ class Controller extends Section {
super($element, $);
const from = new Date();
from.setDate(from.getDate());
from.setDate(from.getDate() - 75);
from.setHours(0, 0, 0, 0);
const to = new Date();

View File

@ -20,7 +20,7 @@
url="Workers/activeWithRole"
search-function="{firstName: $search}"
value-field="id"
wwhere="{role: {inq: ['logistic', 'buyer']}}"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Atender">
<tpl-item>{{nickname}}</tpl-item>
</vn-autocomplete>

View File

@ -172,11 +172,11 @@ module.exports = Self => {
LEFT JOIN zoneEstimatedDelivery zed ON zed.zoneFk = t.zoneFk`);
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(`GROUP BY id`);
stmt.merge(conn.makePagination(filter));
stmts.push(stmt);
stmt = new ParameterizedSQL(`SELECT * FROM tmp.filter`);
stmt.merge(`GROUP BY id`);
stmt.merge(conn.makeOrderBy(filter.order));
const ordersIndex = stmts.push(stmt) - 1;

View File

@ -1,12 +1,13 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('loopback model Supplier', () => {
let supplierOne;
let supplierTwo;
beforeAll(async() => {
supplierOne = await app.models.Supplier.findById(1);
supplierTwo = await app.models.Supplier.findById(442);
supplierOne = await models.Supplier.findById(1);
supplierTwo = await models.Supplier.findById(442);
});
afterAll(async() => {
@ -18,9 +19,9 @@ describe('loopback model Supplier', () => {
it('should throw an error when attempting to set an invalid payMethod id in the supplier', async() => {
let error;
const expectedError = 'You can not select this payment method without a registered bankery account';
const supplier = await app.models.Supplier.findById(1);
const supplier = await models.Supplier.findById(1);
await supplier.updateAttribute('payMethodFk', 4)
await supplier.updateAttribute('payMethodFk', 8)
.catch(e => {
error = e;
@ -31,14 +32,27 @@ describe('loopback model Supplier', () => {
});
it('should not throw if the payMethod id is valid', async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
let error;
const supplier = await app.models.Supplier.findById(442);
const supplier = await models.Supplier.findById(442);
await supplier.updateAttribute('payMethodFk', 4)
.catch(e => {
error = e;
});
expect(error).toBeDefined();
expect(error).not.toBeDefined();
});
});
});

View File

@ -9,40 +9,40 @@
"properties": {
"id": {
"id": true,
"type": "Number",
"type": "number",
"forceId": false
},
"originFk": {
"type": "Number",
"type": "number",
"required": true
},
"userFk": {
"type": "Number"
"type": "number"
},
"action": {
"type": "String",
"type": "string",
"required": true
},
"changedModel": {
"type": "String"
"type": "string"
},
"oldInstance": {
"type": "Object"
"type": "object"
},
"newInstance": {
"type": "Object"
"type": "object"
},
"creationDate": {
"type": "Date"
"type": "date"
},
"changedModelId": {
"type": "String"
"type": "string"
},
"changedModelValue": {
"type": "String"
"type": "string"
},
"description": {
"type": "String"
"type": "string"
}
},
"relations": {

View File

@ -80,7 +80,7 @@ module.exports = Self => {
const supplierAccount = await Self.app.models.SupplierAccount.findOne({where: {supplierFk: this.id}});
const hasIban = supplierAccount && supplierAccount.iban;
if (payMethod && payMethod.ibanRequired && !hasIban)
if (payMethod && payMethod.ibanRequiredForSuppliers && !hasIban)
err();
done();

View File

@ -101,6 +101,9 @@
"mysql": {
"columnName": "withholdingSageFk"
}
},
"isPayMethodChecked": {
"type": "boolean"
}
},
"relations": {
@ -160,13 +163,5 @@
"model": "SupplierAddress",
"foreignKey": "supplierFk"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -36,6 +36,10 @@
label="Active"
ng-model="$ctrl.supplier.isActive">
</vn-check>
<vn-check
label="PayMethodChecked"
ng-model="$ctrl.supplier.isPayMethodChecked">
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textarea

View File

@ -1,4 +1,5 @@
Notes: Notas
Active: Activo
Verified: Verificado
PayMethodChecked: Método de pago validado
Responsible for approving invoices: Responsable de aprobar las facturas

View File

@ -24,7 +24,7 @@
vn-acl="salesAssistant"
ng-model="$ctrl.supplier.payMethodFk"
data="paymethods"
fields="['ibanRequired']"
fields="['ibanRequiredForSuppliers']"
initial-data="$ctrl.supplier.payMethod">
</vn-autocomplete>
<vn-autocomplete

View File

@ -42,27 +42,21 @@ module.exports = Self => {
const stmts = [];
const startedMinusOne = new Date(started);
startedMinusOne.setDate(started.getDate() - 1);
const endedPlusOne = new Date(ended);
endedPlusOne.setDate(ended.getDate() + 1);
stmts.push(`
DROP TEMPORARY TABLE IF EXISTS
tmp.timeControlCalculate,
tmp.timeBusinessCalculate
`);
stmts.push(new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [userId, startedMinusOne, endedPlusOne]));
stmts.push(new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [userId, started, ended]));
stmts.push(new ParameterizedSQL('CALL vn.timeBusiness_calculateByUser(?, ?, ?)', [userId, startedMinusOne, endedPlusOne]));
stmts.push(new ParameterizedSQL('CALL vn.timeBusiness_calculateByUser(?, ?, ?)', [userId, started, ended]));
const resultIndex = stmts.push(new ParameterizedSQL(`
SELECT tbc.dated, tbc.timeWorkSeconds expectedHours, tcc.timeWorkSeconds workedHours
FROM tmp.timeBusinessCalculate tbc
LEFT JOIN tmp.timeControlCalculate tcc ON tcc.dated = tbc.dated
WHERE tbc.dated BETWEEN ? AND ?
SELECT tcc.dated, tbc.timeWorkSeconds expectedHours, tcc.timeWorkSeconds workedHours
FROM tmp.timeControlCalculate tcc
LEFT JOIN tmp.timeBusinessCalculate tbc ON tcc.dated = tbc.dated
WHERE tcc.dated BETWEEN DATE(?) AND DATE(?)
`, [started, ended])) - 1;
stmts.push(`

View File

@ -1,17 +1,17 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('Worker getWorkedHours()', () => {
it(`should return the expected hours and the worked hours of a given date`, async() => {
const workerID = 1106;
let started = new Date();
const started = new Date();
started.setHours(0, 0, 0, 0);
let ended = new Date();
ended.setHours(0, 0, 0, 0);
const ended = new Date();
ended.setHours(23, 59, 59, 999);
const [result] = await app.models.Worker.getWorkedHours(workerID, started, ended);
const [result] = await models.Worker.getWorkedHours(workerID, started, ended);
expect(result.expectedHours).toEqual(28800); // 8:00 hours seconds
expect(result.expectedHours).toEqual(28800); // 8:00 hours in seconds
expect(result.workedHours).toEqual(29400); // 8:10 hours in seconds
});
});

View File

@ -171,7 +171,6 @@ class Controller extends Section {
from: from,
to: to
};
const query = `Workers/${this.$params.id}/getWorkedHours`;
return this.$http.get(query, {params}).then(res => {
const workDays = res.data;
@ -212,7 +211,8 @@ class Controller extends Section {
let todayInWeek = this.weekDays.find(day => day.dated.getTime() === today.getTime());
if (todayInWeek && todayInWeek.hours && todayInWeek.hours.length) {
const remainingTime = todayInWeek.workedHours ? ((todayInWeek.expectedHours - todayInWeek.workedHours) * 1000) : null;
const remainingTime = todayInWeek.workedHours ?
((todayInWeek.expectedHours - todayInWeek.workedHours) * 1000) : null;
const lastKnownEntry = todayInWeek.hours[todayInWeek.hours.length - 1];
const lastKnownTime = new Date(lastKnownEntry.timed).getTime();
const finishTimeStamp = lastKnownTime && remainingTime ? lastKnownTime + remainingTime : null;

View File

@ -0,0 +1,63 @@
module.exports = Self => {
Self.remoteMethod('getEventsFiltered', {
description: 'Get events filtered for zone and date',
accepts: [
{
arg: 'zoneFk',
type: 'number',
description: 'The zone id',
required: true
},
{
arg: 'started',
type: 'date',
description: 'The date calendar start',
},
{
arg: 'ended',
type: 'date',
description: 'The date calendar end',
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/getEventsFiltered`,
verb: 'GET'
}
});
Self.getEventsFiltered = async(zoneFk, started, ended, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
query = `
SELECT *
FROM vn.zoneEvent
WHERE zoneFk = ?
AND ((type = 'indefinitely')
OR (type = 'day' AND dated BETWEEN ? AND ?)
OR (type = 'range'
AND (
(started BETWEEN ? AND ?)
OR (ended BETWEEN ? AND ?)
)
)
)
ORDER BY type='indefinitely' DESC, type='range' DESC, type='day' DESC;`;
const events = await Self.rawSql(query, [zoneFk, started, ended, started, ended, started, ended], myOptions);
query = `
SELECT *
FROM vn.zoneExclusion
WHERE zoneFk = ?
AND dated BETWEEN ? AND ?;`;
const exclusions = await Self.rawSql(query, [zoneFk, started, ended], myOptions);
return {events, exclusions};
};
};

View File

@ -1,12 +1,12 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('agency clone()', () => {
it('should clone a zone', async() => {
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
const newZone = await app.models.Zone.clone(1, options);
const newZone = await models.Zone.clone(1, options);
expect(newZone.name).toEqual('Zone pickup A');

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('zone deletezone()', () => {
@ -16,13 +16,13 @@ describe('zone deletezone()', () => {
active: activeCtx
});
try {
const originalTickets = await app.models.Ticket.find({
const originalTickets = await models.Ticket.find({
where: {
zoneFk: zoneId
}
});
ticketIDs = originalTickets.map(ticket => ticket.id);
originalTicketStates = await app.models.TicketState.find({where: {
originalTicketStates = await models.TicketState.find({where: {
ticketFk: {inq: ticketIDs},
code: 'FIXING'}});
} catch (error) {
@ -31,16 +31,16 @@ describe('zone deletezone()', () => {
});
it('should delete a zone and update their tickets', async() => {
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
await app.models.Zone.deleteZone(ctx, zoneId, options);
await models.Zone.deleteZone(ctx, zoneId, options);
const updatedZone = await app.models.Zone.findById(zoneId, null, options);
const anUpdatedTicket = await app.models.Ticket.findById(ticketIDs[0], null, options);
const updatedZone = await models.Zone.findById(zoneId, null, options);
const anUpdatedTicket = await models.Ticket.findById(ticketIDs[0], null, options);
const updatedTicketStates = await app.models.TicketState.find({
const updatedTicketStates = await models.TicketState.find({
where: {
ticketFk: {inq: ticketIDs},
code: 'FIXING'

View File

@ -1,13 +1,13 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('zone getEvents()', () => {
it('should return all events for the specified geo and agency mode', async() => {
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
let result = await app.models.Zone.getEvents(20, 1, options);
let result = await models.Zone.getEvents(20, 1, options);
expect(result.events.length).toEqual(10);

View File

@ -0,0 +1,21 @@
const models = require('vn-loopback/server/server').models;
describe('zone getEventsFiltered()', () => {
it('should return events and exclusions for the specified zoneFk in a range of dates', async() => {
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
let result = await models.Zone.getEventsFiltered(10, '2021-10-01', '2021-10-02', options);
expect(result.events.length).toEqual(1);
expect(result.exclusions.length).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,13 +1,13 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('zone getLeaves()', () => {
it('should return the country and the childs containing the search value', async() => {
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
let result = await app.models.Zone.getLeaves(1, null, '46000', options);
let result = await models.Zone.getLeaves(1, null, '46000', options);
expect(result.length).toEqual(1);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('zone includingExpired()', () => {
const inhousePickupId = 1;
@ -6,14 +6,14 @@ describe('zone includingExpired()', () => {
const warehouseId = 1;
it('should return an array containing all zones', async() => {
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
const ctx = {req: {accessToken: {userId: 1}}};
const where = {};
try {
const options = {transaction: tx};
const result = await app.models.Zone.includingExpired(ctx, {where}, options);
const result = await models.Zone.includingExpired(ctx, {where}, options);
expect(result.length).toBeGreaterThan(2);
@ -28,12 +28,12 @@ describe('zone includingExpired()', () => {
const ctx = {req: {accessToken: {userId: 1}}};
const where = {agencyModeFk: inhousePickupId};
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
const result = await app.models.Zone.includingExpired(ctx, {where}, options);
const result = await models.Zone.includingExpired(ctx, {where}, options);
const validAgency = result.every(zone => zone.agencyModeFk = inhousePickupId);
@ -56,12 +56,12 @@ describe('zone includingExpired()', () => {
warehouseFk: warehouseId
};
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
const result = await app.models.Zone.includingExpired(ctx, {where}, options);
const result = await models.Zone.includingExpired(ctx, {where}, options);
const firstZone = result[0];
expect(firstZone.name).toEqual('Zone pickup A');

View File

@ -1,13 +1,13 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
describe('zone toggleIsIncluded()', () => {
it('should return the created location with isIncluded true', async() => {
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
let result = await app.models.Zone.toggleIsIncluded(1, 20, true, options);
let result = await models.Zone.toggleIsIncluded(1, 20, true, options);
expect(result.isIncluded).toBeTrue();
@ -19,12 +19,12 @@ describe('zone toggleIsIncluded()', () => {
});
it('should return the created location with isIncluded false', async() => {
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
let result = await app.models.Zone.toggleIsIncluded(1, 20, false, options);
let result = await models.Zone.toggleIsIncluded(1, 20, false, options);
expect(result.isIncluded).toBeFalse();
@ -36,14 +36,14 @@ describe('zone toggleIsIncluded()', () => {
});
it('should return the amount of deleted locations', async() => {
const tx = await app.models.Zone.beginTransaction({});
const tx = await models.Zone.beginTransaction({});
try {
const options = {transaction: tx};
await app.models.Zone.toggleIsIncluded(1, 20, false, options);
await models.Zone.toggleIsIncluded(1, 20, false, options);
let result = await app.models.Zone.toggleIsIncluded(1, 20, undefined, options);
let result = await models.Zone.toggleIsIncluded(1, 20, undefined, options);
expect(result).toEqual({count: 1});

View File

@ -2,6 +2,7 @@ module.exports = Self => {
require('../methods/zone/clone')(Self);
require('../methods/zone/getLeaves')(Self);
require('../methods/zone/getEvents')(Self);
require('../methods/zone/getEventsFiltered')(Self);
require('../methods/zone/toggleIsIncluded')(Self);
require('../methods/zone/getUpcomingDeliveries')(Self);
require('../methods/zone/deleteZone')(Self);

View File

@ -1,5 +1,6 @@
<vn-zone-calendar
id="calendar"
vn-id="calendar"
data="data"
on-selection="$ctrl.onSelection($days, $type, $weekday, $events, $exclusions)"
class="vn-w-md">

View File

@ -21,15 +21,19 @@ class Controller extends Section {
}
refresh() {
let data = {};
this.$q.all([
this.$http.get(this.path)
.then(res => data.events = res.data),
this.$http.get(this.exclusionsPath)
.then(res => data.exclusions = res.data)
]).finally(() => {
this.$.data = null;
this.$.$applyAsync(() => {
const params = {
zoneFk: this.$params.id,
started: this.$.calendar.firstDay,
ended: this.$.calendar.lastDay
};
this.$http.get(`Zones/getEventsFiltered`, {params}).then(res => {
const data = res.data;
this.$.data = data;
});
});
}
formatWdays(weekDays) {

View File

@ -17,8 +17,26 @@ describe('component vnZoneEvents', () => {
describe('refresh()', () => {
it('should set the zone and then call both getSummary() and getWarehouses()', () => {
$httpBackend.expectGET(`Zones/1/events`).respond({id: 1});
$httpBackend.expectGET(`Zones/1/exclusions`).respond({id: 1});
const date = '2021-10-01';
controller.$params.id = 999;
controller.$.calendar = {
firstDay: date,
lastDay: date
};
const params = {
zoneFk: controller.$params.id,
started: date,
ended: date
};
const query = `Zones/getEventsFiltered?ended=${date}&started=${date}&zoneFk=${params.zoneFk}`;
const response = {
events: 'myEvents',
exclusions: 'myExclusions'
};
$httpBackend.whenGET(query).respond(response);
controller.refresh();
$httpBackend.flush();

View File

@ -11,3 +11,4 @@ ticketSubject: Asunto
ticketDescription: Descripción
resolution: Resolución
grafanaLink: "Puedes ver la gráfica desde el siguiente enlace:"
redmineLink: "Desde el siguiente enlace puedes ver el resumen semanal de tareas de refactor:"

View File

@ -33,6 +33,12 @@
https://grafana.verdnatura.es/d/2kaHDi9Mk/osticket?orgId=1&from={{startedTime}}&to={{endedTime}}
</a>
</div>
<p v-html="$t('redmineLink')"></p>
<div class="external-link vn-pa-sm vn-m-md">
<a href="https://redmine.verdnatura.es/projects/refactor/issues?query_id=112" target="_blank">
https://redmine.verdnatura.es/projects/refactor/issues?query_id=112
</a>
</div>
</div>
</div>
<!-- Block -->

View File

@ -12,4 +12,4 @@ claim: Reclamación {0}
sections:
agency:
description: 'Para agilizar su recogida, por favor, póngase en contacto con la oficina
de integrados. <br/> Tlf: 96 166 77 88 - Ana Gómez (Ext. 2113) <em>(agomezf@integra2.com)</em>'
de Logista Parcel. <br/> Tlf: 96 166 77 88 - Ana Gómez (Ext. 2113) <em>(atcsalidas.i2valencia@integra2.es)</em>'

View File

@ -12,8 +12,9 @@ SELECT
FROM claim cl
JOIN client c ON c.id = cl.clientFk
JOIN account.user u ON u.id = c.id
JOIN country ct ON ct.id = c.countryFk
JOIN ticket t ON t.id = cl.ticketFk
JOIN address a ON a.id = t.addressFk
LEFT JOIN province p ON p.id = c.provinceFk
LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN autonomy amy ON amy.id = p.autonomyFk
LEFT JOIN country ct ON ct.id = amy.countryFk
WHERE cl.id = ?

View File

@ -96,7 +96,7 @@
</div>
</div>
<div class="pull-left">
<h2>Shipped</h2>
<h2>{{$t('shipped')}}</h2>
</div>
<div class="pull-left">
<div class="field rectangle">

View File

@ -5,3 +5,4 @@ SELECT
FROM invoiceOut io
JOIN ticket t ON t.refFk = io.ref
WHERE io.id = ?
ORDER BY t.shipped