diff --git a/client/claim/src/descriptor/index.html b/client/claim/src/descriptor/index.html
index 4bf70e5fea..5fc417e8a7 100644
--- a/client/claim/src/descriptor/index.html
+++ b/client/claim/src/descriptor/index.html
@@ -20,10 +20,10 @@
-
-
diff --git a/client/claim/src/detail/index.html b/client/claim/src/detail/index.html
new file mode 100644
index 0000000000..068765e2a4
--- /dev/null
+++ b/client/claim/src/detail/index.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+ Detail
+
+
+
+ Id
+ Landed
+ Quantity
+ Claimed
+ Description
+ Price
+ Disc.
+ Total
+
+
+
+
+ {{saleClaimed.sale.id}}
+ {{saleClaimed.sale.ticket.landed | dateTime: 'dd/MM/yyyy'}}
+ {{saleClaimed.sale.quantity}}
+
+
+
+
+ {{saleClaimed.sale.concept}}
+ {{saleClaimed.sale.price | currency:'€':2}}
+ {{saleClaimed.sale.discount}} %
+
+ {{(saleClaimed.sale.quantity * saleClaimed.sale.price) -
+ ((saleClaimed.sale.discount *
+ (saleClaimed.sale.quantity * saleClaimed.sale.price))/100) | currency:'€':2
+ }}
+
+
+
+
+
+
+
+
+ No results
+
+
+
+
+
+
+
+
+
+
+
+
+ Claimable sales from ticket
{{$ctrl.claim.ticketFk}}
+
+
+
+ Id
+ Landed
+ Quantity
+ Description
+ Price
+ Disc.
+ Total
+
+
+
+
+ {{sale.saleFk}}
+ {{sale.landed | dateTime: 'dd/MM/yyyy'}}
+ {{sale.quantity}}
+ {{sale.concept}}
+ {{sale.price | currency:'€':2}}
+ {{sale.discount}} %
+
+ {{(sale.quantity * sale.price) - ((sale.discount * (sale.quantity * sale.price))/100) | currency:'€':2}}
+
+
+
+
+ No results
+
+
+
+
\ No newline at end of file
diff --git a/client/claim/src/detail/index.js b/client/claim/src/detail/index.js
new file mode 100644
index 0000000000..ae353f903a
--- /dev/null
+++ b/client/claim/src/detail/index.js
@@ -0,0 +1,84 @@
+import ngModule from '../module';
+import './style.scss';
+
+class Controller {
+ constructor($state, $scope, $http, $translate, vnApp) {
+ this.$state = $state;
+ this.$ = $scope;
+ this.$http = $http;
+ this.$translate = $translate;
+ this.vnApp = vnApp;
+ this.filter = {
+ where: {claimFk: $state.params.id},
+ include: [
+ {relation: 'sale',
+ scope: {
+ fields: ['concept', 'ticketFk', 'price', 'quantity', 'discount'],
+ include: {
+ relation: 'ticket'
+ }
+ }
+ }
+ ]
+ };
+ }
+
+ openAddSalesDialog() {
+ this.getClaimableFromTicket();
+ this.$.addSales.show();
+ }
+
+ getClaimableFromTicket() {
+ let json = encodeURIComponent(JSON.stringify(this.claim.ticketFk));
+
+ let query = `/api/Sales/getClaimableFromTicket?ticketFk=${json}`;
+ this.$http.get(query).then(res => {
+ if (res.data) {
+ this.salesToClaim = res.data;
+ }
+ });
+ }
+
+ addClaimedSale(saleFk) {
+ let saleToAdd = {saleFk: saleFk, claimFk: this.claim.id, quantity: 0};
+ let query = `claim/api/ClaimBeginnings/`;
+ this.$http.post(query, saleToAdd).then(() => {
+ this.$.model.refresh();
+ this.$.addSales.hide();
+ this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
+ });
+ }
+
+ deleteClaimedSale(id) {
+ let json = encodeURIComponent(JSON.stringify(id));
+ let query = `claim/api/ClaimBeginnings/${json}`;
+ this.$http.delete(query).then(() => {
+ this.$.model.refresh();
+ this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
+ });
+ }
+
+ focusLastInput() {
+ let inputs = document.querySelectorAll("#claimedQuantity");
+ inputs[inputs.length - 1].querySelector("input").select();
+ }
+
+ setClaimedQuantity(id, claimedQuantity) {
+ let params = {id: id, quantity: claimedQuantity};
+ let query = `claim/api/ClaimBeginnings/`;
+ this.$http.patch(query, params).then(() => {
+ this.$.model.refresh();
+ this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
+ });
+ }
+}
+
+Controller.$inject = ['$state', '$scope', '$http', '$translate', 'vnApp'];
+
+ngModule.component('vnClaimDetail', {
+ template: require('./index.html'),
+ controller: Controller,
+ bindings: {
+ claim: '<'
+ }
+});
diff --git a/client/claim/src/detail/index.spec.js b/client/claim/src/detail/index.spec.js
new file mode 100644
index 0000000000..d6769a022a
--- /dev/null
+++ b/client/claim/src/detail/index.spec.js
@@ -0,0 +1,94 @@
+import './index.js';
+
+describe('claim', () => {
+ describe('Component vnClaimDetail', () => {
+ let $componentController;
+ let controller;
+ let $httpBackend;
+ let $state;
+
+ beforeEach(() => {
+ angular.mock.module('claim');
+ });
+
+ beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_, $rootScope) => {
+ $componentController = _$componentController_;
+ $httpBackend = _$httpBackend_;
+ $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
+ $httpBackend.when('GET', 'claim/api/Claims/ClaimBeginnings').respond({});
+ $state = _$state_;
+ $state.params.id = 1;
+
+ controller = $componentController('vnClaimDetail', {$state: $state});
+ controller.claim = {ticketFk: 1};
+ controller.$.model = {refresh: () => {}};
+ controller.$.addSales = {
+ hide: () => {},
+ show: () => {}
+ };
+ }));
+
+ describe('openAddSalesDialog()', () => {
+ it('should call getClaimableFromTicket and $.addSales.show', () => {
+ controller.$ = {addSales: {show: () => {}}};
+ spyOn(controller, 'getClaimableFromTicket');
+ spyOn(controller.$.addSales, 'show');
+ controller.openAddSalesDialog();
+
+ expect(controller.getClaimableFromTicket).toHaveBeenCalledWith();
+ expect(controller.$.addSales.show).toHaveBeenCalledWith();
+ });
+ });
+
+ describe('getClaimableFromTicket()', () => {
+ it('should make a query and set salesToClaim', () => {
+ $httpBackend.expectGET(`/api/Sales/getClaimableFromTicket?ticketFk=1`).respond(200, 1);
+ controller.getClaimableFromTicket();
+ $httpBackend.flush();
+
+ expect(controller.salesToClaim).toEqual(1);
+ });
+ });
+
+ describe('addClaimedSale(saleFk)', () => {
+ it('should make a post and call refresh, hide and showSuccess', () => {
+ spyOn(controller.$.model, 'refresh');
+ spyOn(controller.$.addSales, 'hide');
+ spyOn(controller.vnApp, 'showSuccess');
+ $httpBackend.expectPOST(`claim/api/ClaimBeginnings/`).respond({});
+ controller.addClaimedSale(1);
+ $httpBackend.flush();
+
+ expect(controller.$.model.refresh).toHaveBeenCalledWith();
+ expect(controller.$.addSales.hide).toHaveBeenCalledWith();
+ expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
+ });
+ });
+
+ describe('deleteClaimedSale(id)', () => {
+ it('should make a delete and call refresh and showSuccess', () => {
+ spyOn(controller.$.model, 'refresh');
+ spyOn(controller.vnApp, 'showSuccess');
+ $httpBackend.expectDELETE(`claim/api/ClaimBeginnings/1`).respond({});
+ controller.deleteClaimedSale(1);
+ $httpBackend.flush();
+
+ expect(controller.$.model.refresh).toHaveBeenCalledWith();
+ expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
+ });
+ });
+
+ describe('setClaimedQuantity(id, claimedQuantity)', () => {
+ it('should make a patch and call refresh and showSuccess', () => {
+ spyOn(controller.$.model, 'refresh');
+ spyOn(controller.vnApp, 'showSuccess');
+ $httpBackend.expectPATCH(`claim/api/ClaimBeginnings/`).respond({});
+ controller.setClaimedQuantity(1, 1);
+ $httpBackend.flush();
+
+ expect(controller.$.model.refresh).toHaveBeenCalledWith();
+ expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
+ });
+ });
+ });
+});
diff --git a/client/claim/src/detail/locale/es.yml b/client/claim/src/detail/locale/es.yml
new file mode 100644
index 0000000000..bda82a4600
--- /dev/null
+++ b/client/claim/src/detail/locale/es.yml
@@ -0,0 +1,7 @@
+Claimed: Reclamados
+Disc.: Dto.
+Attended by: Atendida por
+Landed: Recibido
+Price: Precio
+Claimable sales from ticket: Lineas reclamables del ticket
+Detail: Detalles
\ No newline at end of file
diff --git a/client/claim/src/detail/style.scss b/client/claim/src/detail/style.scss
new file mode 100644
index 0000000000..cb66b31ab1
--- /dev/null
+++ b/client/claim/src/detail/style.scss
@@ -0,0 +1,20 @@
+vn-claim-detail {
+ vn-textfield {
+ margin: 0!important;
+ max-width: 100px;
+ }
+ vn-dialog[vn-id=addSales] {
+ tpl-body {
+ width: 950px;
+ div {
+ div.buttons {
+ display: none;
+ }
+ vn-table{
+ min-width: 950px;
+ }
+ }
+ }
+ }
+}
+
diff --git a/client/claim/src/index.js b/client/claim/src/index.js
index 54099a5ba2..e779cd5956 100644
--- a/client/claim/src/index.js
+++ b/client/claim/src/index.js
@@ -2,6 +2,7 @@ export * from './module';
import './index/';
import './card';
+import './detail';
import './descriptor';
import './basic-data';
// import './summary';
diff --git a/client/core/src/directives/index.js b/client/core/src/directives/index.js
index 07ce5dfe23..a97c88b3c8 100644
--- a/client/core/src/directives/index.js
+++ b/client/core/src/directives/index.js
@@ -9,4 +9,4 @@ import './zoom-image';
import './visible-by';
import './bind';
import './repeat-last';
-import './title';
\ No newline at end of file
+import './title';
diff --git a/services/claim/common/models/claim-beginning.js b/services/claim/common/models/claim-beginning.js
new file mode 100644
index 0000000000..bce69d5315
--- /dev/null
+++ b/services/claim/common/models/claim-beginning.js
@@ -0,0 +1,7 @@
+module.exports = Self => {
+ // Validations
+
+ Self.validatesUniquenessOf('saleFk', {
+ message: `A claim with that sale already exists`
+ });
+};
diff --git a/services/loopback/common/methods/sale/getClaimableFromTicket.js b/services/loopback/common/methods/sale/getClaimableFromTicket.js
new file mode 100644
index 0000000000..adfb917103
--- /dev/null
+++ b/services/loopback/common/methods/sale/getClaimableFromTicket.js
@@ -0,0 +1,44 @@
+module.exports = Self => {
+ Self.remoteMethod('getClaimableFromTicket', {
+ description: 'Gets the claimable sales for a client',
+ accessType: 'READ',
+ accepts: [{
+ arg: 'ticketFk',
+ type: 'Number',
+ required: true,
+ description: 'ticketFk'
+ }],
+ returns: {
+ type: 'string',
+ root: true
+ },
+ http: {
+ path: `/getClaimableFromTicket`,
+ verb: 'GET'
+ }
+ });
+
+ Self.getClaimableFromTicket = async ticketFk => {
+ let query = `SELECT
+ s.id AS saleFk,
+ t.id AS ticketFk,
+ t.landed,
+ s.concept,
+ s.itemFk,
+ s.quantity,
+ s.price,
+ s.discount,
+ t.nickname
+ FROM vn.ticket t
+ INNER JOIN vn.sale s ON s.ticketFk = t.id
+ LEFT JOIN vn.claimBeginning cb ON cb.saleFk = s.id
+
+ WHERE (t.landed) >= TIMESTAMPADD(DAY, -7, CURDATE())
+ AND t.id = ? AND cb.id IS NULL
+ ORDER BY t.landed DESC, t.id DESC`;
+
+ let claimableSales = await Self.rawSql(query, [ticketFk]);
+
+ return claimableSales;
+ };
+};
diff --git a/services/loopback/common/models/sale.js b/services/loopback/common/models/sale.js
index 47feb07d80..7d7a0f8691 100644
--- a/services/loopback/common/models/sale.js
+++ b/services/loopback/common/models/sale.js
@@ -1,5 +1,6 @@
module.exports = Self => {
require('../methods/sale/filter')(Self);
+ require('../methods/sale/getClaimableFromTicket')(Self);
require('../methods/sale/saleComponentFilter')(Self);
require('../methods/sale/priceDifference')(Self);
require('../methods/sale/moveToTicket')(Self);