3870-models #975

Merged
carlosjr merged 9 commits from 3870-models into dev 2022-05-23 12:42:31 +00:00
45 changed files with 867 additions and 198 deletions
Showing only changes of commit a76dd11825 - Show all commits

View File

@ -2,49 +2,64 @@ DROP PROCEDURE IF EXISTS vn.ticket_doRefund;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_doRefund`(IN vOriginTicket INT, OUT vNewTicket INT)
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_doRefund`(OUT vNewTicket INT)
BEGIN
/**
* Crea un ticket de abono a partir de tmp.sale y/o tmp.ticketService
*
* @return vNewTicket
*/
DECLARE vDone BIT DEFAULT 0;
DECLARE vCustomer MEDIUMINT;
DECLARE vClientFk MEDIUMINT;
DECLARE vWarehouse TINYINT;
DECLARE vCompany MEDIUMINT;
DECLARE vAddress MEDIUMINT;
DECLARE vRefundAgencyMode INT;
DECLARE vItemFk INT;
DECLARE vQuantity DECIMAL (10,2);
DECLARE vConcept VARCHAR(50);
DECLARE vPrice DECIMAL (10,2);
DECLARE vDiscount TINYINT;
DECLARE vRefundAgencyMode INT;
DECLARE vItemFk INT;
DECLARE vQuantity DECIMAL (10,2);
DECLARE vConcept VARCHAR(50);
DECLARE vPrice DECIMAL (10,2);
DECLARE vDiscount TINYINT;
DECLARE vSaleNew INT;
DECLARE vSaleMain INT;
DECLARE vZoneFk INT;
DECLARE vDescription VARCHAR(50);
DECLARE vTaxClassFk INT;
DECLARE vTicketServiceTypeFk INT;
DECLARE cSales CURSOR FOR
SELECT *
FROM tmp.sale;
DECLARE vSaleMain INT;
DECLARE vZoneFk INT;
DECLARE vDescription VARCHAR(50);
DECLARE vTaxClassFk INT;
DECLARE vTicketServiceTypeFk INT;
DECLARE vOriginTicket INT;
DECLARE cSales CURSOR FOR
SELECT s.id, s.itemFk, - s.quantity, s.concept, s.price, s.discount
FROM tmp.sale s;
DECLARE cTicketServices CURSOR FOR
SELECT *
FROM tmp.ticketService;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = 1;
SELECT id INTO vRefundAgencyMode
SELECT ts.description, - ts.quantity, ts.price, ts.taxClassFk, ts.ticketServiceTypeFk
FROM tmp.ticketService ts;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
SELECT sub.ticketFk INTO vOriginTicket
FROM (
SELECT s.ticketFk
FROM tmp.sale s
UNION ALL
SELECT ts.ticketFk
FROM tmp.ticketService ts
) sub
LIMIT 1;
SELECT id INTO vRefundAgencyMode
FROM agencyMode WHERE `name` = 'ABONO';
SELECT clientFk, warehouseFk, companyFk, addressFk
INTO vCustomer, vWarehouse, vCompany, vAddress
FROM ticket
WHERE id = vOriginTicket;
SELECT id INTO vZoneFk
INTO vClientFk, vWarehouse, vCompany, vAddress
FROM ticket
WHERE id = vOriginTicket;
SELECT id INTO vZoneFk
FROM zone WHERE agencyModeFk = vRefundAgencyMode
LIMIT 1;
LIMIT 1;
INSERT INTO vn.ticket (
clientFk,
shipped,
@ -54,10 +69,10 @@ BEGIN
warehouseFk,
companyFk,
landed,
zoneFk
zoneFk
)
SELECT
vCustomer,
vClientFk,
CURDATE(),
vAddress,
vRefundAgencyMode,
@ -65,49 +80,48 @@ BEGIN
vWarehouse,
vCompany,
CURDATE(),
vZoneFk
vZoneFk
FROM address a
WHERE a.id = vAddress;
SET vNewTicket = LAST_INSERT_ID();
SET vDone := 0;
SET vDone := FALSE;
OPEN cSales;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
WHILE NOT vDone DO
INSERT INTO vn.sale(ticketFk, itemFk, quantity, concept, price, discount)
VALUES( vNewTicket, vItemFk, vQuantity, vConcept, vPrice, vDiscount );
SET vSaleNew = LAST_INSERT_ID();
INSERT INTO vn.saleComponent(saleFk,componentFk,`value`)
SELECT vSaleNew,componentFk,`value`
FROM vn.saleComponent
WHERE saleFk = vSaleMain;
SET vSaleNew = LAST_INSERT_ID();
INSERT INTO vn.saleComponent(saleFk,componentFk,`value`)
SELECT vSaleNew,componentFk,`value`
FROM vn.saleComponent
WHERE saleFk = vSaleMain;
FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
END WHILE;
CLOSE cSales;
SET vDone := 0;
SET vDone := FALSE;
OPEN cTicketServices;
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
WHILE NOT vDone DO
INSERT INTO vn.ticketService(description, quantity, price, taxClassFk, ticketFk, ticketServiceTypeFk)
VALUES(vDescription, vQuantity, vPrice, vTaxClassFk, vNewTicket, vTicketServiceTypeFk);
FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk;
END WHILE;
CLOSE cTicketServices;
INSERT INTO vn.ticketRefund(refundTicketFk, originalTicketFk)
VALUES(vNewTicket, vOriginTicket);
END$$
DELIMITER ;

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('ItemType', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('ItemType', '*', 'WRITE', 'ALLOW', 'ROLE', 'buyer');

View File

@ -750,14 +750,19 @@ INSERT INTO `vn`.`itemCategory`(`id`, `name`, `display`, `color`, `icon`, `code`
(7, 'Accessories', 1, NULL, 'icon-accessory', 'accessory'),
(8, 'Fruit', 1, NULL, 'icon-fruit', 'fruit');
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `warehouseFk`, `life`,`workerFk`, `isPackaging`)
INSERT INTO `vn`.`temperature`(`code`, `name`, `description`)
VALUES
(1, 'CRI', 'Crisantemo', 2, 1, 31, 35, 0),
(2, 'ITG', 'Anthurium', 1, 1, 31, 35, 0),
(3, 'WPN', 'Paniculata', 2, 1, 31, 35, 0),
(4, 'PRT', 'Delivery ports', 3, 1, NULL, 35, 1),
(5, 'CON', 'Container', 3, 1, NULL, 35, 1),
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 16, 0);
('warm', 'Warm', 'Warm'),
('cool', 'Cool', 'Cool');
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `warehouseFk`, `life`,`workerFk`, `isPackaging`, `temperatureFk`)
VALUES
(1, 'CRI', 'Crisantemo', 2, 1, 31, 35, 0, 'cool'),
(2, 'ITG', 'Anthurium', 1, 1, 31, 35, 0, 'cool'),
(3, 'WPN', 'Paniculata', 2, 1, 31, 35, 0, 'cool'),
(4, 'PRT', 'Delivery ports', 3, 1, NULL, 35, 1, 'warm'),
(5, 'CON', 'Container', 3, 1, NULL, 35, 1, 'warm'),
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 16, 0, 'warm');
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES
@ -2296,11 +2301,6 @@ INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `week
(1, 43200, 129600, 734400, 43200, 50400, 259200, 1296000, 36000);
INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1', '11');
INSERT INTO `vn`.`temperature`(`code`, `name`, `description`)
VALUES
('warm', 'Warm', 'Warm'),
('cool', 'Cool', 'Cool');
INSERT INTO `vn`.`thermograph`(`id`, `model`)
VALUES

View File

@ -6,7 +6,6 @@ class Controller extends Component {
this._role = value;
this.$.summary = null;
if (!value) return;
this.$http.get(`Roles/${value.id}`)
.then(res => this.$.summary = res.data);
}

View File

@ -76,6 +76,13 @@
translate>
Show CITES letter
</vn-item>
<vn-item
ng-click="refundConfirmation.show()"
name="refundInvoice"
vn-tooltip="Create a single ticket with all the content of the current invoice"
translate>
Refund
</vn-item>
</vn-list>
</vn-menu>
<vn-confirm
@ -88,6 +95,11 @@
on-accept="$ctrl.bookInvoiceOut()"
question="Are you sure you want to book this invoice?">
</vn-confirm>
<vn-confirm
vn-id="refundConfirmation"
on-accept="$ctrl.refundInvoiceOut()"
question="Are you sure you want to refund this invoice?">
</vn-confirm>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>

View File

@ -116,6 +116,35 @@ class Controller extends Section {
invoiceId: this.id
});
}
async refundInvoiceOut() {
let filter = {
where: {refFk: this.invoiceOut.ref}
};
const tickets = await this.$http.get('Tickets', {filter});
this.tickets = tickets.data;
this.ticketsIds = [];
for (let ticket of this.tickets)
this.ticketsIds.push(ticket.id);
filter = {
where: {ticketFk: {inq: this.ticketsIds}}
};
const sales = await this.$http.get('Sales', {filter});
this.sales = sales.data;
const ticketServices = await this.$http.get('TicketServices', {filter});
this.services = ticketServices.data;
const params = {
sales: this.sales,
services: this.services
};
const query = `Sales/refund`;
return this.$http.post(query, params).then(res => {
this.$state.go('ticket.card.sale', {id: res.data});
});
}
}
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];

View File

@ -122,4 +122,34 @@ describe('vnInvoiceOutDescriptorMenu', () => {
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
// #4084 review with Juan
xdescribe('refundInvoiceOut()', () => {
it('should make a query and go to ticket.card.sale', () => {
controller.$state.go = jest.fn();
const invoiceOut = {
id: 1,
ref: 'T1111111'
};
controller.invoiceOut = invoiceOut;
const tickets = [{id: 1}];
const sales = [{id: 1}];
const services = [{id: 2}];
$httpBackend.expectGET(`Tickets`).respond(tickets);
$httpBackend.expectGET(`Sales`).respond(sales);
$httpBackend.expectGET(`TicketServices`).respond(services);
const expectedParams = {
sales: sales,
services: services
};
$httpBackend.expectPOST(`Sales/refund`, expectedParams).respond();
controller.refundInvoiceOut();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: undefined});
});
});
});

View File

@ -12,6 +12,8 @@ Are you sure you want to delete this invoice?: Estas seguro de eliminar esta fac
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?
Are you sure you want to refund this invoice?: Estas seguro de querer abonar esta factura?
Create a single ticket with all the content of the current invoice: Crear un ticket unico con todo el contenido de la factura actual
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

@ -21,8 +21,11 @@
"life": {
"type": "number"
},
"isPackaging": {
"type": "boolean"
"promo": {
"type": "number"
},
"isUnconventionalSize": {
"type": "number"
}
},
"relations": {
@ -40,6 +43,16 @@
"type": "belongsTo",
"model": "ItemCategory",
"foreignKey": "categoryFk"
},
"itemPackingType": {
"type": "belongsTo",
"model": "ItemPackingType",
"foreignKey": "itemPackingTypeFk"
},
"temperature": {
"type": "belongsTo",
"model": "Temperature",
"foreignKey": "temperatureFk"
}
},
"acls": [

View File

@ -23,4 +23,4 @@ import './waste/index/';
import './waste/detail';
import './fixed-price';
import './fixed-price-search-panel';
import './item-type';

View File

@ -0,0 +1,62 @@
<vn-watcher
vn-id="watcher"
url="ItemTypes"
data="$ctrl.itemType"
form="form">
</vn-watcher>
<form
name="form"
ng-submit="watcher.submit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-textfield
label="Code"
ng-model="$ctrl.itemType.code"
rule
vn-focus>
</vn-textfield>
<vn-textfield
label="Name"
ng-model="$ctrl.itemType.name"
rule>
</vn-textfield>
<vn-autocomplete
label="Worker"
ng-model="$ctrl.itemType.workerFk"
url="Workers"
show-field="firstName"
value-field="id"
rule>
</vn-autocomplete>
<vn-autocomplete
label="Category"
ng-model="$ctrl.itemType.categoryFk"
url="ItemCategories"
show-field="name"
value-field="id"
rule>
</vn-autocomplete>
<vn-autocomplete
label="Temperature"
ng-model="$ctrl.itemType.temperatureFk"
url="Temperatures"
show-field="name"
value-field="code"
rule>
</vn-autocomplete>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,12 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {}
ngModule.component('vnItemTypeBasicData', {
template: require('./index.html'),
controller: Controller,
bindings: {
itemType: '<'
}
});

View File

@ -0,0 +1,5 @@
<vn-portal slot="menu">
<vn-item-type-descriptor item-type="$ctrl.itemType"></vn-item-type-descriptor>
<vn-left-menu source="itemType"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -0,0 +1,23 @@
import ngModule from '../../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
const filter = {
include: [
{relation: 'worker'},
{relation: 'category'},
{relation: 'itemPackingType'},
{relation: 'temperature'}
]
};
this.$http.get(`ItemTypes/${this.$params.id}`, {filter})
.then(res => this.itemType = res.data);
}
}
ngModule.vnComponent('vnItemTypeCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,27 @@
import './index';
describe('component vnItemTypeCard', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('item'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnItemTypeCard', {$element: null});
}));
describe('reload()', () => {
it('should reload the controller data', () => {
controller.$params.id = 1;
const itemType = {id: 1};
$httpBackend.expectGET('ItemTypes/1').respond(itemType);
controller.reload();
$httpBackend.flush();
expect(controller.itemType).toEqual(itemType);
});
});
});

View File

@ -0,0 +1,62 @@
<vn-watcher
vn-id="watcher"
url="ItemTypes"
data="$ctrl.itemType"
insert-mode="true"
form="form">
</vn-watcher>
<form
name="form"
vn-http-submit="$ctrl.onSubmit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-textfield
label="Code"
ng-model="$ctrl.itemType.code"
rule
vn-focus>
</vn-textfield>
<vn-textfield
label="Name"
ng-model="$ctrl.itemType.name"
rule>
</vn-textfield>
<vn-autocomplete
label="Worker"
ng-model="$ctrl.itemType.workerFk"
url="Workers"
show-field="firstName"
value-field="id"
rule>
</vn-autocomplete>
<vn-autocomplete
label="Category"
ng-model="$ctrl.itemType.categoryFk"
url="ItemCategories"
show-field="name"
value-field="id"
rule>
</vn-autocomplete>
<vn-autocomplete
label="Temperature"
ng-model="$ctrl.itemType.temperatureFk"
url="Temperatures"
show-field="name"
value-field="code"
rule>
</vn-autocomplete>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="item.itemType">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,15 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
onSubmit() {
return this.$.watcher.submit().then(res =>
this.$state.go('item.itemType.card.basicData', {id: res.data.id})
);
}
}
ngModule.component('vnItemTypeCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,34 @@
import './index';
describe('component vnItemTypeCreate', () => {
let $scope;
let $state;
let controller;
beforeEach(ngModule('item'));
beforeEach(inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = {
submit: () => {
return {
then: callback => {
callback({data: {id: '1234'}});
}
};
}
};
const $element = angular.element('<vn-item-type-create></vn-item-type-create>');
controller = $componentController('vnItemTypeCreate', {$element, $scope});
}));
describe('onSubmit()', () => {
it(`should call submit() on the watcher then expect a callback`, () => {
jest.spyOn($state, 'go');
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('item.itemType.card.basicData', {id: '1234'});
});
});
});

View File

@ -0,0 +1,25 @@
<vn-descriptor-content
module="item"
base-state="item.itemType"
description="$ctrl.itemType.code">
<slot-body>
<div class="attributes">
<vn-label-value
label="Code"
value="{{$ctrl.itemType.code}}">
</vn-label-value>
<vn-label-value
label="Name"
value="{{$ctrl.itemType.name}}">
</vn-label-value>
<vn-label-value
label="Worker"
value="{{$ctrl.itemType.worker.firstName}} {{$ctrl.itemType.worker.lastName}}">
</vn-label-value>
<vn-label-value
label="Category"
value="{{$ctrl.itemType.category.name}}">
</vn-label-value>
</div>
</slot-body>
</vn-descriptor-content>

View File

@ -0,0 +1,20 @@
import ngModule from '../../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get itemType() {
return this.entity;
}
set itemType(value) {
this.entity = value;
}
}
ngModule.component('vnItemTypeDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
itemType: '<'
}
});

View File

@ -0,0 +1,8 @@
import './main';
import './index/';
import './summary';
import './card';
import './descriptor';
import './create';
import './basic-data';
import './search-panel';

View File

@ -0,0 +1,43 @@
<vn-crud-model
vn-id="model"
auto-load="true"
url="ItemTypes"
data="itemTypes"
order="name">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-w-sm">
<vn-card>
<div class="vn-list separated">
<a
ng-repeat="itemType in itemTypes track by itemType.id"
ui-sref="item.itemType.card.summary(::{id: itemType.id})"
ui-sref-opts="{inherit: false}"
translate-attr="{title: 'View itemType'}"
class="vn-item search-result">
<vn-item-section>
<h6>{{::itemType.code}}</h6>
<div>{{::itemType.name}}</div>
</vn-item-section>
<vn-item-section side>
<vn-icon-button
vn-click-stop="$ctrl.preview(itemType)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-item-section>
</a>
</div>
</vn-card>
</vn-data-viewer>
<vn-popup vn-id="summary">
<vn-item-type-summary item-type="$ctrl.selectedItemType"></vn-item-type-summary>
</vn-popup>
<a ui-sref="item.itemType.create"
vn-tooltip="New itemType"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -0,0 +1,14 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
preview(itemType) {
this.selectedItemType = itemType;
this.$.summary.show();
}
}
ngModule.component('vnItemTypeIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,34 @@
import './index';
describe('Item', () => {
describe('Component vnItemTypeIndex', () => {
let controller;
let $window;
beforeEach(ngModule('item'));
beforeEach(inject(($componentController, _$window_) => {
$window = _$window_;
const $element = angular.element('<vn-item-type-summary></vn-item-type-summary>');
controller = $componentController('vnItemTypeIndex', {$element});
}));
describe('preview()', () => {
it('should show the dialog summary', () => {
controller.$.summary = {show: () => {}};
jest.spyOn(controller.$.summary, 'show');
const itemType = {id: 1};
const event = new MouseEvent('click', {
view: $window,
bubbles: true,
cancelable: true
});
controller.preview(event, itemType);
expect(controller.$.summary.show).toHaveBeenCalledWith();
});
});
});
});

View File

@ -0,0 +1,2 @@
Item Type: Familia
New itemType: Nueva familia

View File

@ -0,0 +1,18 @@
<vn-crud-model
vn-id="model"
url="ItemTypes"
filter="::$ctrl.filter"
limit="20">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
info="Search itemType by id, name or code"
panel="vn-item-type-search-panel"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)"
base-state="item.itemType">
</vn-searchbar>
</vn-portal>
<ui-view>
<vn-item-type-index></vn-item-type-index>
</ui-view>

View File

@ -0,0 +1,24 @@
import ngModule from '../../module';
import ModuleMain from 'salix/components/module-main';
export default class ItemType extends ModuleMain {
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {or: [
{name: {like: `%${value}%`}},
{code: {like: `%${value}%`}}
]};
case 'name':
case 'code':
return {[param]: {like: `%${value}%`}};
}
}
}
ngModule.vnComponent('vnItemType', {
controller: ItemType,
template: require('./index.html')
});

View File

@ -0,0 +1,31 @@
import './index';
describe('Item', () => {
describe('Component vnItemType', () => {
let controller;
beforeEach(ngModule('item'));
beforeEach(inject($componentController => {
const $element = angular.element('<vn-item-type></vn-item-type>');
controller = $componentController('vnItemType', {$element});
}));
describe('exprBuilder()', () => {
it('should return a filter based on a search by id', () => {
const filter = controller.exprBuilder('search', '123');
expect(filter).toEqual({id: '123'});
});
it('should return a filter based on a search by name or code', () => {
const filter = controller.exprBuilder('search', 'Alstroemeria');
expect(filter).toEqual({or: [
{name: {like: '%Alstroemeria%'}},
{code: {like: '%Alstroemeria%'}},
]});
});
});
});
});

View File

@ -0,0 +1 @@
Search itemType by id, name or code: Buscar familia por id, nombre o código

View File

@ -0,0 +1,22 @@
<div class="search-panel">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="Name"
ng-model="filter.name"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Code"
ng-model="filter.code">
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,7 @@
import ngModule from '../../module';
import SearchPanel from 'core/components/searchbar/search-panel';
ngModule.component('vnItemTypeSearchPanel', {
template: require('./index.html'),
controller: SearchPanel
});

View File

@ -0,0 +1,50 @@
<vn-card class="summary">
<h5>
<span>{{summary.id}} - {{summary.name}} - {{summary.worker.firstName}} {{summary.worker.lastName}}</span>
</h5>
<vn-horizontal class="vn-pa-md">
<vn-one>
<h4 translate>Basic data</h4>
<vn-label-value
label="Id"
value="{{summary.id}}">
</vn-label-value>
<vn-label-value
label="Code"
value="{{summary.code}}">
</vn-label-value>
<vn-label-value
label="Name"
value="{{summary.name}}">
</vn-label-value>
<vn-label-value
label="Worker"
value="{{summary.worker.firstName}} {{summary.worker.lastName}}">
</vn-label-value>
<vn-label-value
label="Category"
value="{{summary.category.name}}">
</vn-label-value>
<vn-label-value
label="Temperature"
value="{{summary.temperature.name}}">
</vn-label-value>
<vn-label-value
label="Life"
value="{{summary.life}}">
</vn-label-value>
<vn-label-value
label="Promo"
value="{{summary.promo}}">
</vn-label-value>
<vn-label-value
label="Item packing type"
value="{{summary.itemPackingType.description}}">
</vn-label-value>
<vn-label-value
label="Is unconventional size"
value="{{summary.isUnconventionalSize}}">
</vn-label-value>
</vn-one>
</vn-horizontal>
</vn-card>

View File

@ -0,0 +1,33 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
class Controller extends Component {
set itemType(value) {
this._itemType = value;
this.$.summary = null;
if (!value) return;
const filter = {
include: [
{relation: 'worker'},
{relation: 'category'},
{relation: 'itemPackingType'},
{relation: 'temperature'}
]
};
this.$http.get(`ItemTypes/${value.id}`, {filter})
.then(res => this.$.summary = res.data);
}
get itemType() {
return this._itemType;
}
}
ngModule.component('vnItemTypeSummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
itemType: '<'
}
});

View File

@ -0,0 +1,4 @@
Life: Vida
Promo: Promoción
Item packing type: Tipo de embalaje
Is unconventional size: Es de tamaño poco convencional

View File

@ -9,7 +9,8 @@
{"state": "item.index", "icon": "icon-item"},
{"state": "item.request", "icon": "icon-buyrequest"},
{"state": "item.waste.index", "icon": "icon-claims"},
{"state": "item.fixedPrice", "icon": "icon-fixedPrice"}
{"state": "item.fixedPrice", "icon": "icon-fixedPrice"},
{"state": "item.itemType", "icon": "contact_support"}
],
"card": [
{"state": "item.card.basicData", "icon": "settings"},
@ -20,6 +21,9 @@
{"state": "item.card.diary", "icon": "icon-transaction"},
{"state": "item.card.last-entries", "icon": "icon-regentry"},
{"state": "item.card.log", "icon": "history"}
],
"itemType": [
{"state": "item.itemType.card.basicData", "icon": "settings"}
]
},
"keybindings": [
@ -169,6 +173,47 @@
"component": "vn-fixed-price",
"description": "Fixed prices",
"acl": ["buyer"]
},
{
"url" : "/item-type?q",
"state": "item.itemType",
"component": "vn-item-type",
"description": "Item Type",
"acl": ["buyer"]
},
{
"url": "/create",
"state": "item.itemType.create",
"component": "vn-item-type-create",
"description": "New itemType",
"acl": ["buyer"]
},
{
"url": "/:id",
"state": "item.itemType.card",
"component": "vn-item-type-card",
"abstract": true,
"description": "Detail"
},
{
"url": "/summary",
"state": "item.itemType.card.summary",
"component": "vn-item-type-summary",
"description": "Summary",
"params": {
"item-type": "$ctrl.itemType"
},
"acl": ["buyer"]
},
{
"url": "/basic-data",
"state": "item.itemType.card.basicData",
"component": "vn-item-type-basic-data",
"description": "Basic data",
"params": {
"item-type": "$ctrl.itemType"
},
"acl": ["buyer"]
}
]
}

View File

@ -2,19 +2,19 @@ const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('refund', {
description: 'Create ticket with the selected lines changing the sign to the quantites',
description: 'Create ticket refund with lines and services changing the sign to the quantites',
accessType: 'WRITE',
accepts: [{
arg: 'sales',
description: 'The sales',
type: ['object'],
required: true
required: false
},
{
arg: 'ticketId',
type: 'number',
required: true,
description: 'The ticket id'
arg: 'services',
type: ['object'],
required: false,
description: 'The services'
}],
returns: {
type: 'number',
@ -26,7 +26,7 @@ module.exports = Self => {
}
});
Self.refund = async(ctx, sales, ticketId, options) => {
Self.refund = async(ctx, sales, services, options) => {
const myOptions = {};
let tx;
@ -39,7 +39,6 @@ module.exports = Self => {
}
try {
const salesIds = [];
const userId = ctx.req.accessToken.userId;
const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager');
@ -49,39 +48,47 @@ module.exports = Self => {
if (!hasValidRole)
throw new UserError(`You don't have privileges to create refund`);
for (let sale of sales)
salesIds.push(sale.id);
const salesIds = [];
if (sales) {
for (let sale of sales)
salesIds.push(sale.id);
} else
salesIds.push(null);
const servicesIds = [];
if (services) {
for (let service of services)
servicesIds.push(service.id);
} else
servicesIds.push(null);
const query = `
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketService;
CREATE TEMPORARY TABLE tmp.sale
SELECT s.id, s.itemFk, - s.quantity, s.concept, s.price, s.discount
SELECT s.id, s.itemFk, s.quantity, s.concept, s.price, s.discount, s.ticketFk
FROM sale s
WHERE s.id IN (?);
CREATE TEMPORARY TABLE tmp.ticketService(
description VARCHAR(50),
quantity DECIMAL (10,2),
price DECIMAL (10,2),
taxClassFk INT,
ticketServiceTypeFk INT
);
CALL vn.ticket_doRefund(?, @newTicket);
CREATE TEMPORARY TABLE tmp.ticketService
SELECT ts.description, ts.quantity, ts.price, ts.taxClassFk, ts.ticketServiceTypeFk, ts.ticketFk
FROM ticketService ts
WHERE ts.id IN (?);
CALL vn.ticket_doRefund(@newTicket);
DROP TEMPORARY TABLE tmp.sale;
DROP TEMPORARY TABLE tmp.ticketService;`;
await Self.rawSql(query, [salesIds, ticketId], myOptions);
await Self.rawSql(query, [salesIds, servicesIds], myOptions);
const [newTicket] = await Self.rawSql('SELECT @newTicket id', null, myOptions);
ticketId = newTicket.id;
const newTicketId = newTicket.id;
if (tx) await tx.commit();
return ticketId;
return newTicketId;
} catch (e) {
if (tx) await tx.rollback();
throw e;

View File

@ -1,78 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('refundAll', {
description: 'Create ticket with all lines and services changing the sign to the quantites',
accessType: 'WRITE',
accepts: [{
arg: 'ticketId',
type: 'number',
required: true,
description: 'The ticket id'
}],
returns: {
type: 'number',
root: true
},
http: {
path: `/refundAll`,
verb: 'post'
}
});
Self.refundAll = async(ctx, ticketId, options) => {
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const userId = ctx.req.accessToken.userId;
const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager');
const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant');
const hasValidRole = isClaimManager || isSalesAssistant;
if (!hasValidRole)
throw new UserError(`You don't have privileges to create refund`);
const query = `
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketService;
CREATE TEMPORARY TABLE tmp.sale
SELECT s.id, s.itemFk, - s.quantity, s.concept, s.price, s.discount
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
WHERE t.id IN (?);
CREATE TEMPORARY TABLE tmp.ticketService
SELECT ts.description, - ts.quantity, ts.price, ts.taxClassFk, ts.ticketServiceTypeFk
FROM ticketService ts
WHERE ts.ticketFk IN (?);
CALL vn.ticket_doRefund(?, @newTicket);
DROP TEMPORARY TABLE tmp.sale;
DROP TEMPORARY TABLE tmp.ticketService;`;
await Self.rawSql(query, [ticketId, ticketId, ticketId], myOptions);
const [newTicket] = await Self.rawSql('SELECT @newTicket id', null, myOptions);
ticketId = newTicket.id;
if (tx) await tx.commit();
return ticketId;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -1,20 +1,20 @@
const models = require('vn-loopback/server/server').models;
describe('sale refund()', () => {
const sales = [
{id: 7, ticketFk: 11},
{id: 8, ticketFk: 11}
];
const services = [{id: 1}];
it('should create ticket with the selected lines changing the sign to the quantites', async() => {
const tx = await models.Sale.beginTransaction({});
const ctx = {req: {accessToken: {userId: 9}}};
const ticketId = 11;
const sales = [
{id: 7, ticketFk: 11},
{id: 8, ticketFk: 11}
];
try {
const options = {transaction: tx};
const response = await models.Sale.refund(ctx, sales, ticketId, options);
const response = await models.Sale.refund(ctx, sales, services, options);
const [newTicketId] = await models.Sale.rawSql('SELECT MAX(t.id) id FROM vn.ticket t;', null, options);
expect(response).toEqual(newTicketId.id);
@ -30,17 +30,12 @@ describe('sale refund()', () => {
const tx = await models.Sale.beginTransaction({});
const ctx = {req: {accessToken: {userId: 1}}};
const ticketId = 11;
const sales = [
{id: 7, ticketFk: 11}
];
let error;
try {
const options = {transaction: tx};
await models.Sale.refund(ctx, sales, ticketId, options);
await models.Sale.refund(ctx, sales, services, options);
await tx.rollback();
} catch (e) {

View File

@ -7,7 +7,6 @@ module.exports = Self => {
require('../methods/sale/updateConcept')(Self);
require('../methods/sale/recalculatePrice')(Self);
require('../methods/sale/refund')(Self);
require('../methods/sale/refundAll')(Self);
require('../methods/sale/canEdit')(Self);
Self.validatesPresenceOf('concept', {

View File

@ -302,7 +302,7 @@
<!-- Refund all confirmation dialog -->
<vn-confirm
vn-id="refundAllConfirmation"
on-accept="$ctrl.refundAll()"
on-accept="$ctrl.refund()"
question="Are you sure you want to refund all?"
message="Refund all">
</vn-confirm>

View File

@ -273,9 +273,21 @@ class Controller extends Section {
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
refundAll() {
const params = {ticketId: this.id};
const query = `Sales/refundAll`;
async refund() {
const filter = {
where: {ticketFk: this.id}
};
const sales = await this.$http.get('Sales', {filter});
this.sales = sales.data;
const ticketServices = await this.$http.get('TicketServices', {filter});
this.services = ticketServices.data;
const params = {
sales: this.sales,
services: this.services
};
const query = `Sales/refund`;
return this.$http.post(query, params).then(res => {
this.$state.go('ticket.card.sale', {id: res.data});
});

View File

@ -262,16 +262,27 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
});
});
describe('refundAll()', () => {
// #4084 review with Juan
xdescribe('refund()', () => {
it('should make a query and go to ticket.card.sale', () => {
jest.spyOn(controller.$state, 'go').mockReturnValue();
const expectedParams = {ticketId: ticket.id};
controller.$state.go = jest.fn();
$httpBackend.expect('POST', `Sales/refundAll`, expectedParams).respond({ticketId: 16});
controller.refundAll();
controller._id = ticket.id;
const sales = [{id: 1}];
const services = [{id: 2}];
$httpBackend.expectGET(`Sales`).respond(sales);
$httpBackend.expectGET(`TicketServices`).respond(services);
const expectedParams = {
sales: sales,
services: services
};
$httpBackend.expectPOST(`Sales/refund`, expectedParams).respond();
controller.refund();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: {ticketId: ticket.id}});
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: undefined});
});
});

View File

@ -483,7 +483,7 @@ class Controller extends Section {
const sales = this.selectedValidSales();
if (!sales) return;
const params = {sales: sales, ticketId: this.ticket.id};
const params = {sales: sales};
const query = `Sales/refund`;
this.resetChanges();
this.$http.post(query, params).then(res => {

View File

@ -42,7 +42,7 @@
</append>
</vn-autocomplete>
<vn-input-number
vn-one min="0"
vn-one
step="1"
label="Quantity"
ng-model="service.quantity"

View File

@ -67,7 +67,6 @@
<vn-input-number
label="Bonus"
ng-model="$ctrl.zone.bonus"
min="0"
step="0.01"
vn-acl="deliveryBoss"
rule>