contable 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/client/client.js +259 -0
  4. package/dist/client/img/bannersuperior.png +0 -0
  5. package/dist/client/menu.js +3 -0
  6. package/dist/client/redux-formulario.js +379 -0
  7. package/dist/client/render-components.js +750 -0
  8. package/dist/client/render-components.tsx +1117 -0
  9. package/dist/client/render-formulario.js +56 -0
  10. package/dist/client/render-formulario.tsx +72 -0
  11. package/dist/client/render-general.js +197 -0
  12. package/dist/client/render-general.tsx +212 -0
  13. package/dist/client/render-handlers.js +69 -0
  14. package/dist/client/tipos.js +13 -0
  15. package/dist/client/tsconfig.json +12 -0
  16. package/dist/server/app-contable.js +174 -0
  17. package/dist/server/def-config.js +100 -0
  18. package/dist/server/procedures-contable.js +508 -0
  19. package/dist/server/server-contable.js +17 -0
  20. package/dist/server/table-categorias_producto.js +29 -0
  21. package/dist/server/table-clientes.js +56 -0
  22. package/dist/server/table-condicion_iva.js +29 -0
  23. package/dist/server/table-estados_pedido.js +33 -0
  24. package/dist/server/table-estados_presupuesto.js +34 -0
  25. package/dist/server/table-insumos.js +44 -0
  26. package/dist/server/table-items_pedido.js +48 -0
  27. package/dist/server/table-items_presupuesto.js +48 -0
  28. package/dist/server/table-iva_productos.js +29 -0
  29. package/dist/server/table-parametros.js +38 -0
  30. package/dist/server/table-pedidos.js +48 -0
  31. package/dist/server/table-pedidos_pendientes.js +28 -0
  32. package/dist/server/table-pedidos_vencidos.js +28 -0
  33. package/dist/server/table-presupuestos.js +56 -0
  34. package/dist/server/table-productos.js +70 -0
  35. package/dist/server/table-productos_clientes.js +22 -0
  36. package/dist/server/table-productos_generales.js +22 -0
  37. package/dist/server/table-productos_insumos.js +47 -0
  38. package/dist/server/table-usuarios.js +44 -0
  39. package/dist/server/tsconfig.json +10 -0
  40. package/dist/server/types-contable.js +28 -0
  41. package/dist/unlogged/adapt.js +8 -0
  42. package/dist/unlogged/css/styles.styl +88 -0
  43. package/dist/unlogged/img/login-logo-icon.png.png +0 -0
  44. package/dist/unlogged/img/logo.png +0 -0
  45. package/dist/unlogged/tsconfig.json +15 -0
  46. package/dist/unlogged/unlogged.js +205 -0
  47. package/install/categorias_producto.tab +2 -0
  48. package/install/condicion_iva.tab +3 -0
  49. package/install/estados_pedido.tab +5 -0
  50. package/install/estados_presupuesto.tab +5 -0
  51. package/install/iva_productos.tab +3 -0
  52. package/install/productos.tab +2 -0
  53. package/install/usuarios.tab +2 -0
  54. package/package.json +58 -0
  55. package/unlogged/img/main-loading.gif +0 -0
  56. package/unlogged/main-loading.gif +0 -0
  57. package/unlogged/my-skin.js +0 -0
@@ -0,0 +1,1117 @@
1
+ import * as React from "react";
2
+ import * as ReactDOM from "react-dom";
3
+ import {
4
+ FocusOpts, RenderPrincipal,
5
+ clsx,
6
+ ICON,
7
+ focusToId,
8
+ scrollToTop,
9
+ scrollToBottom,
10
+ InputTypes, totalsStyles, AltoImpresionStyle
11
+ } from "./render-general";
12
+ import { dispatchers, crearStore, ActionFormularioState, guardarPresupuesto,
13
+ createInitialStatePresupuesto, guardarPedido, createInitialStatePedido, inicializarPedido } from "./redux-formulario";
14
+ import {
15
+ AppBar, Badge, Button, ButtonGroup, Card, Checkbox, Chip, CircularProgress, CssBaseline,
16
+ Dialog, DialogActions, DialogContent, DialogContentText,
17
+ DialogTitle, Divider, Fab, FormControlLabel, Grid, IconButton, InputBase, InputLabel,
18
+ Link, List, ListItem, ListItemIcon, ListItemText, Drawer,
19
+ Menu, MenuItem, Paper, Popover,
20
+ Select, Snackbar, Step, Stepper, StepContent, StepLabel,
21
+ SvgIcon, Switch,
22
+ Table, TableBody, TableCell, TableHead, TableRow, TextField, Theme, Toolbar, Typography, Zoom,
23
+ useScrollTrigger,
24
+ createStyles, makeStyles, Icon, Popper, Grow, ClickAwayListener, MenuList, FormControl, TableContainer, withStyles, InputAdornment, InputProps
25
+ } from "@material-ui/core";
26
+ import { useState, useEffect, useRef } from "react";
27
+ import { useSelector, useDispatch, Provider } from "react-redux";
28
+ import * as likeAr from "like-ar";
29
+ import {saveDepot} from "./render-handlers";
30
+ import { Cliente, Producto, Item, State, DatosPresupuesto, IdCondIva, DatosPedido, DatosDesde, Presupuesto, Pedido, IdEstadoPresupuesto } from "./tipos";
31
+ import { Store } from "redux";
32
+ import { ActionsFrom } from "redux-typed-reducer";
33
+
34
+ export const ESTADO_ACEPTADO_PRESUPUESTO: IdEstadoPresupuesto = "aceptado";
35
+ var my=myOwn;
36
+
37
+ export var generarPedidoDesdePresupuesto = (datosPresupuesto:DatosPresupuesto)=>{
38
+ let {cliente, items, presupuesto} = datosPresupuesto;
39
+ let datosPedido:DatosPedido = {
40
+ cliente,
41
+ items,
42
+ pedido:{
43
+ presupuesto: presupuesto.presupuesto,
44
+ cliente: presupuesto.cliente,
45
+ descuento: presupuesto.descuento,
46
+ subtotal: presupuesto.subtotal,
47
+ total: presupuesto.total,
48
+ ...inicializarPedido()
49
+ }
50
+ }
51
+ return datosPedido
52
+ }
53
+
54
+
55
+
56
+
57
+ my.wScreens.presupuesto=async function(addrParams){
58
+ var datosPresupuesto: DatosPresupuesto|null=null;
59
+ //@ts-ignore existe
60
+ if(addrParams.presupuesto){
61
+ //@ts-ignore existe
62
+ datosPresupuesto = await my.ajax.presupuesto_cargar({presupuesto:addrParams.presupuesto});
63
+ }
64
+ var store = await crearStore({useSessionStorage:true, datosPresupuesto: datosPresupuesto || undefined}, createInitialStatePresupuesto);
65
+ desplegarPresupuesto(store);
66
+ }
67
+
68
+ my.wScreens.pedido=async function(addrParams){
69
+ var datosPedido: DatosPedido|null=null;
70
+ //@ts-ignore existe
71
+ if(addrParams.pedido){
72
+ //@ts-ignore existe
73
+ datosPedido = await my.ajax.pedido_cargar({pedido:addrParams.pedido});
74
+ //@ts-ignore existe
75
+ }else if(addrParams.presupuesto){
76
+ //@ts-ignore existe
77
+ var datosPresupuesto: DatosPresupuesto = await my.ajax.presupuesto_cargar({presupuesto:addrParams.presupuesto});
78
+ datosPedido = generarPedidoDesdePresupuesto(datosPresupuesto);
79
+ }
80
+ var store = await crearStore({useSessionStorage:true, datosPedido: datosPedido || undefined}, createInitialStatePedido);
81
+ desplegarPedido(store);
82
+ }
83
+
84
+
85
+
86
+ const useStyles = makeStyles((theme) => ({
87
+ appBar: {
88
+ position: 'relative',
89
+ },
90
+ layout: {
91
+ width: 'auto',
92
+ marginLeft: theme.spacing(2),
93
+ marginRight: theme.spacing(2),
94
+ [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
95
+ //width: 600,
96
+ //marginLeft: 'auto',
97
+ //marginRight: 'auto',
98
+ },
99
+ },
100
+ paper: {
101
+ marginTop: theme.spacing(2),
102
+ marginBottom: theme.spacing(2),
103
+ padding: theme.spacing(2),
104
+ [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
105
+ marginTop: theme.spacing(2),
106
+ marginBottom: theme.spacing(2),
107
+ padding: theme.spacing(3),
108
+ },
109
+ },
110
+ stepper: {
111
+ padding: theme.spacing(3, 0, 5),
112
+ },
113
+ buttons: {
114
+ display: 'flex',
115
+ justifyContent: 'flex-end',
116
+ },
117
+ button: {
118
+ marginTop: theme.spacing(1),
119
+ marginBottom: theme.spacing(1),
120
+ },
121
+ }));
122
+
123
+ export function Autocompleter(props:{
124
+ rows:any[],
125
+ value:any,
126
+ searchKey:string,
127
+ searchType:'text'|'number',
128
+ onSelection:(row:any)=>void
129
+ id: string,
130
+ className?:string,
131
+ label?: string,
132
+ name?: string,
133
+ disabled?:boolean
134
+ multiline?:boolean
135
+ matchInListConditionCallback?:(elem:Producto)=>boolean
136
+ }){
137
+ const {rows, value, searchKey, searchType, onSelection, id, className, label, name, matchInListConditionCallback} = props;
138
+ const anchorRef = useRef<HTMLTableDataCellElement>(null);
139
+ const [filteredRows, setFilteredRows] = useState<any[]>([]);
140
+ const [open, setOpen] = useState<boolean>(false);
141
+ const [focusOnList, setFocusOnList] = useState<boolean>(false);
142
+ const [valorIngresado, setValorIngresado] = useState<string|null>(null);
143
+ useEffect(() => {
144
+ setValorIngresado(value)
145
+ },[value]);
146
+ useEffect(() => {
147
+ document.body.style.overflow=open?'hidden':'unset'
148
+ });
149
+ const focusToId = (id:string, opts:FocusOpts, cb?:(e:HTMLElement)=>void)=>{
150
+ var element=document.getElementById(id);
151
+ var padre=element;
152
+ while(padre && padre.className!='pregunta' && padre!=document.body) padre = padre.parentElement;
153
+ if(padre && padre.className=='pregunta') element=padre;
154
+ if(element){
155
+ if(cb){
156
+ cb(element);
157
+ }else{
158
+ element.focus();
159
+ if(opts.moveToElement){
160
+ element.scrollIntoView();
161
+ window.scroll({behavior:opts.moveBehavior||'auto', top:window.scrollY-100, left:0})
162
+ }
163
+ }
164
+ }
165
+ }
166
+ const checkIfValue = () => {
167
+ var result = rows.find((row)=>row[searchKey].toString().toLowerCase()==(valorIngresado||'').toLowerCase());
168
+ if(result){
169
+ setValorIngresado(result[searchKey]);
170
+ onSelection(result);
171
+ setOpen(false)
172
+ }else{
173
+ setValorIngresado(null);
174
+ setFilteredRows([]);
175
+ onSelection(null);
176
+ setOpen(false);
177
+ }
178
+ }
179
+
180
+ const handleClose = (event: React.MouseEvent<EventTarget>) => {
181
+ if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
182
+ return;
183
+ }
184
+ checkIfValue();
185
+ //y al borrar con backspace primero marca si no está marcado y después borra
186
+ focusToId(id, {moveToElement:false});
187
+ };
188
+
189
+ const handleSelection = (row:any)=>{
190
+ onSelection(row);
191
+ setValorIngresado(row[searchKey]);
192
+ setFilteredRows([]);
193
+ setOpen(false);
194
+ focusToId(id, {moveToElement:false});
195
+ }
196
+ return <>
197
+ <TextField
198
+ id={id}
199
+ className={className}
200
+ spellCheck={false}
201
+ autoCapitalize="off"
202
+ autoComplete="off"
203
+ autoCorrect="off"
204
+ multiline={props.multiline}
205
+ name={name}
206
+ label={label}
207
+ ref={anchorRef}
208
+ fullWidth
209
+ type={searchType}
210
+ disabled={props.disabled || false}
211
+ value={valorIngresado || ''}
212
+ onChange={(event)=>{
213
+ let value = event.target.value;
214
+ setValorIngresado(event.target.value || null);
215
+ setFilteredRows(rows.filter((row)=>
216
+ value.split(' ').every(palabraBuscada=>
217
+ row[searchKey].toString().toLocaleLowerCase().includes(palabraBuscada.toLocaleLowerCase()) &&
218
+ (matchInListConditionCallback?matchInListConditionCallback(row):true)
219
+ )
220
+ ));
221
+ setOpen(event.target.value.length>1 && filteredRows.length <= 10)
222
+ }}
223
+ onFocus={(_event)=> {
224
+ setFocusOnList(false);
225
+ //setOpen(!!valorIngresado && valorIngresado.length>1);
226
+ }}
227
+ onBlur={()=>{
228
+ if(!open){
229
+ checkIfValue()
230
+ }
231
+ }}
232
+ onKeyDown={(event)=>{
233
+ //down key
234
+ if(event.keyCode == 40){
235
+ if(open){
236
+ setFocusOnList(true)
237
+ }
238
+ //enter key or tab key
239
+ }else if([13,9].includes(event.keyCode)){
240
+ checkIfValue()
241
+ }
242
+ }}
243
+ aria-controls={open ? 'menu-list-grow' : undefined}
244
+ aria-haspopup="true"
245
+ />
246
+
247
+ <Popper open={open} anchorEl={anchorRef.current} placement="bottom-start" transition>
248
+ {({ TransitionProps, placement }) => (
249
+ <Grow
250
+ {...TransitionProps}
251
+ style={{ transformOrigin: placement === 'bottom' ? 'left top' : 'center bottom' }}
252
+ >
253
+ <Paper>
254
+ <ClickAwayListener onClickAway={handleClose}>
255
+ <MenuList autoFocusItem={focusOnList} id="menu-list-grow">
256
+ {
257
+ filteredRows.length?
258
+ filteredRows.map((row, i)=>
259
+ <MenuItem
260
+ key={i}
261
+ value={i}
262
+ onClick={()=>handleSelection(row)}
263
+ onKeyDown={(event)=>{
264
+ //esc key
265
+ if(event.keyCode == 27 && open){
266
+ setOpen(false)
267
+ focusToId(id, {moveToElement:false});
268
+ }
269
+ //tab
270
+ if(event.keyCode == 9){
271
+ handleSelection(row);
272
+ }
273
+ }}
274
+ >
275
+ {row[searchKey]}
276
+ </MenuItem>
277
+ )
278
+ :
279
+ <MenuItem
280
+ key={-1}
281
+ disabled
282
+ >
283
+ Sin resultados
284
+ </MenuItem>
285
+ }
286
+ </MenuList>
287
+ </ClickAwayListener>
288
+ </Paper>
289
+ </Grow>
290
+ )}
291
+ </Popper>
292
+ </>
293
+ }
294
+ export function TypedField(props:{
295
+ textAlign?:'left'|'right'|'center'|'justify'|'inherit'
296
+ value:string|number|null,
297
+ disabled?:boolean
298
+ type: 'text'|'number'|'date'|'time'|'tel',
299
+ id:string,
300
+ className?:string,
301
+ name?:string,
302
+ label?:string
303
+ onChange:(value:string|number|null)=>void
304
+
305
+ matchInList?:any[],
306
+ multiline?:boolean,
307
+ inputAdornment?:string
308
+ adornmentPosition?:'start'|'end'
309
+ }){
310
+ const {value, type, id, className, name, label, onChange, matchInList, disabled, textAlign, inputAdornment, adornmentPosition} = props;
311
+ useEffect(() => {
312
+ setMyValue(value)
313
+ },[value]);
314
+ const [myValue, setMyValue] = useState(value);
315
+ var inputProps={
316
+ style: { textAlign: textAlign }
317
+
318
+ }
319
+ var inputLabelProps={
320
+ shrink: true,
321
+ }
322
+ var adornmentProps:InputProps = {}
323
+ var adornment = inputAdornment?<InputAdornment position="end">{inputAdornment}</InputAdornment>:null;
324
+ adornmentPosition=='start'?
325
+ adornmentProps.startAdornment=adornment
326
+ :
327
+ adornmentProps.endAdornment=adornment
328
+ return <TextField
329
+ inputProps={inputProps}
330
+ spellCheck={false}
331
+ autoCapitalize="off"
332
+ autoComplete="off"
333
+ autoCorrect="off"
334
+ value={myValue==null?'':myValue}
335
+ disabled={disabled||false}
336
+ id={id}
337
+ multiline={props.multiline}
338
+ className={className}
339
+ name={name}
340
+ label={label}
341
+ type={type}
342
+ fullWidth
343
+ onChange={(event)=>{
344
+ var value = event.target.value;
345
+ setMyValue(value==''?null:value)
346
+ }}
347
+ onBlur={()=>{
348
+ var value = matchInList?matchInList.find((elem)=>elem==myValue):myValue;
349
+ setMyValue(value);
350
+ onChange(myValue)
351
+ }}
352
+ InputLabelProps={inputLabelProps}
353
+ InputProps={adornmentProps}
354
+ />
355
+ }
356
+
357
+ export function ClienteComponent(props:{editando:boolean, datosDesde:DatosDesde}){
358
+ const {datosDesde} = props;
359
+ const {clientes, condicionesIva, clienteActual} = useSelector((estado:State)=>({clientes:estado.estructura.clientes, condicionesIva:estado.estructura.condicionesIva, clienteActual:estado[datosDesde].cliente}));
360
+ const clientesArr = likeAr(clientes).array();
361
+ const dispatch = useDispatch();
362
+ const condicionesIvaArr = likeAr(condicionesIva).array();
363
+ const classes = useStyles();
364
+ return <Paper className={classes.paper}>
365
+ <Grid container spacing={3}>
366
+ <Grid item xs={12} sm={4} md={1}>
367
+ <TypedField
368
+ value={clienteActual.cliente}
369
+ type="number"
370
+ id="cliente"
371
+ name="cliente"
372
+ label="Cliente"
373
+ matchInList={clientesArr.map((cliente)=>cliente.cliente)}
374
+ disabled={!props.editando}
375
+ onChange={(value)=>{
376
+ var searchResult=null;
377
+ if(value){
378
+ searchResult = clientesArr.find((cliente)=>cliente.cliente==value) || null
379
+ }
380
+ dispatch(dispatchers.SET_CLIENTE({cliente:{...searchResult}, datosDesde}))
381
+ }}
382
+ />
383
+ </Grid>
384
+ <Grid item xs={12} sm={8} md={5}>
385
+ <Autocompleter
386
+ rows={clientesArr}
387
+ value={clienteActual.razon_social || ''}
388
+ id="razon-social"
389
+ name="razon-social"
390
+ label="Razon social"
391
+ searchKey="razon_social"
392
+ searchType="text"
393
+ disabled={!props.editando}
394
+ onSelection={(clienteSeleccionado:Cliente)=>dispatch(dispatchers.SET_CLIENTE({cliente:{...clienteSeleccionado}, datosDesde}))}
395
+ />
396
+ </Grid>
397
+ <Grid item xs={12} sm={6} md={3}>
398
+ <FormControl fullWidth>
399
+ <InputLabel id="condicion_iva_label">Cond. IVA</InputLabel>
400
+ <Select
401
+ disabled={true}
402
+ labelId="condicion_iva_label"
403
+ id="condicion_iva"
404
+ value={clienteActual.condicion_iva || ''}
405
+ onChange={(event)=>{
406
+ var condicion_iva = event.target.value as IdCondIva||null;
407
+ dispatch(dispatchers.SET_CLIENTE({cliente:{...clienteActual, condicion_iva}, datosDesde}))
408
+ }}
409
+ >
410
+ {condicionesIvaArr.map((condicionIva)=>
411
+ <MenuItem key={condicionIva.condicion} value={condicionIva.condicion}>{condicionIva.nombre}</MenuItem>
412
+ )}
413
+ </Select>
414
+ </FormControl>
415
+ </Grid>
416
+ <Grid item xs={12} sm={6} md={3}>
417
+ <TypedField
418
+ type="number"
419
+ disabled={true}
420
+ value={clienteActual.cuit_dni}
421
+ id="cuit_dni"
422
+ label="CUIT/DNI"
423
+ name="cuit_dni"
424
+ onChange={
425
+ (value)=>dispatch(dispatchers.SET_CLIENTE({cliente:{...clienteActual, cuit_dni:Number(value)}, datosDesde})
426
+ )}
427
+ />
428
+ </Grid>
429
+ </Grid>
430
+ </Paper>
431
+ }
432
+
433
+ export function EstadoPresupuesto(props:{editando:boolean}){
434
+ const {presupuesto, estadosPresupuesto} = useSelector((estado:State)=>({presupuesto: estado.datosPresupuesto.presupuesto, estadosPresupuesto: estado.estructura.estadosPresupuesto}));
435
+ const dispatch = useDispatch();
436
+ const classes = useStyles();
437
+ return <Paper className={classes.paper}>
438
+ <Grid container spacing={3}>
439
+ <Grid item xs={12} sm={4}>
440
+ <FormControl fullWidth>
441
+ <InputLabel id="estado_label">Estado</InputLabel>
442
+ <Select
443
+ labelId="estado_label"
444
+ id="estado"
445
+ value={presupuesto.estado || ''}
446
+ disabled={true}
447
+ onChange={(event)=>{
448
+ var estado = event.target.value as string||null;
449
+ dispatch(dispatchers.SET_CAMPO_PRESUPUESTO({campo:"estado", valor:estado}))
450
+ }}
451
+ >
452
+ {estadosPresupuesto
453
+ .map((estado)=>
454
+ <MenuItem key={estado.estado} value={estado.estado}>{estado.nombre}</MenuItem>
455
+ )
456
+ }
457
+ </Select>
458
+ </FormControl>
459
+ </Grid>
460
+ <Grid item xs={12} sm={4}>
461
+ <TypedField
462
+ type="date"
463
+ value={presupuesto.fecha}
464
+ id="fecha"
465
+ label="fecha"
466
+ name="fecha"
467
+ disabled={!props.editando}
468
+ onChange={
469
+ ((value)=>dispatch(dispatchers.SET_CAMPO_PRESUPUESTO({campo:"fecha", valor:value}))
470
+ )}
471
+ />
472
+ </Grid>
473
+ <Grid item xs={12} sm={4}>
474
+ <TypedField
475
+ type="date"
476
+ value={presupuesto.valido_hasta}
477
+ id="valido_hasta"
478
+ label="valido hasta"
479
+ name="valido_hasta"
480
+ disabled={!props.editando}
481
+ onChange={
482
+ ((value)=>dispatch(dispatchers.SET_CAMPO_PRESUPUESTO({campo:"valido_hasta", valor:value}))
483
+ )}
484
+ />
485
+ </Grid>
486
+ </Grid>
487
+ </Paper>
488
+ }
489
+
490
+ export function EstadoPedido(props:{editando:boolean}){
491
+ const {pedido, estadosPedido} = useSelector((estado:State)=>({pedido: estado.datosPedido.pedido, estadosPedido: estado.estructura.estadosPedido}));
492
+ const dispatch = useDispatch();
493
+ const classes = useStyles();
494
+ return <Paper className={classes.paper}>
495
+ <Grid container spacing={3}>
496
+ <Grid item xs={12} sm={4}>
497
+ <FormControl fullWidth>
498
+ <InputLabel id="estado_label">Estado</InputLabel>
499
+ <Select
500
+ labelId="estado_label"
501
+ id="estado"
502
+ value={pedido.estado || ''}
503
+ disabled={!props.editando}
504
+ onChange={(event)=>{
505
+ var estado = event.target.value as string||null;
506
+ dispatch(dispatchers.SET_CAMPO_PEDIDO({campo:"estado", valor:estado}))
507
+ }}
508
+ >
509
+ {estadosPedido.map((estado)=>
510
+ <MenuItem key={estado.estado} value={estado.estado}>{estado.nombre}</MenuItem>
511
+ )}
512
+ </Select>
513
+ </FormControl>
514
+ </Grid>
515
+ <Grid item xs={12} sm={4}>
516
+ <TypedField
517
+ type="date"
518
+ value={pedido.fecha}
519
+ id="fecha"
520
+ label="fecha"
521
+ name="fecha"
522
+ disabled={!props.editando}
523
+ onChange={
524
+ ((value)=>dispatch(dispatchers.SET_CAMPO_PEDIDO({campo:"fecha", valor:value}))
525
+ )}
526
+ />
527
+ </Grid>
528
+ <Grid item xs={12} sm={4}>
529
+ <TypedField
530
+ type="date"
531
+ value={pedido.fecha_entrega}
532
+ id="fecha_entrega"
533
+ label="fecha entrega"
534
+ name="fecha_entrega"
535
+ disabled={!props.editando}
536
+ onChange={
537
+ ((value)=>dispatch(dispatchers.SET_CAMPO_PEDIDO({campo:"fecha_entrega", valor:value}))
538
+ )}
539
+ />
540
+ </Grid>
541
+ </Grid>
542
+ </Paper>
543
+ }
544
+ export function ObservacionesPedido(props:{editando:boolean}){
545
+ const {pedido} = useSelector((estado:State)=>({pedido: estado.datosPedido.pedido}));
546
+ const dispatch = useDispatch();
547
+ const classes = useStyles();
548
+ return <Paper className={classes.paper}>
549
+ <Grid container spacing={3}>
550
+ <Grid item xs={12} sm={12}>
551
+ <TypedField
552
+ type="text"
553
+ disabled={!props.editando}
554
+ value={pedido.observaciones}
555
+ id="observaciones"
556
+ label="Observaciones"
557
+ name="observaciones"
558
+ onChange={
559
+ (value)=>dispatch(dispatchers.SET_CAMPO_PEDIDO({campo:"observaciones", valor:value}))
560
+ }
561
+ />
562
+ </Grid>
563
+ </Grid>
564
+ </Paper>
565
+ }
566
+
567
+ export function Items(props:{
568
+ editando:boolean,
569
+ conDescuentoEnItems:boolean,
570
+ conOpciones: boolean,
571
+ datosDesde:DatosDesde
572
+ presupuestoOPedido:Presupuesto|Pedido
573
+ }){
574
+ const {conDescuentoEnItems, conOpciones, datosDesde, presupuestoOPedido} = props;
575
+ const {items, cliente} = useSelector((estado:State)=>estado[datosDesde]);
576
+ const classes = useStyles();
577
+ const dispatch = useDispatch();
578
+ const handleAdd = ()=>{
579
+ dispatch(dispatchers.ADD_ITEM({datosDesde}))
580
+ setTimeout(()=>focusToId("cantidad_"+(items.length-1).toString(),{moveToElement:false}),100)
581
+ }
582
+ return <TableContainer component={Paper}>
583
+ <Table aria-label="customized table" id="items-table">
584
+ <TableHead>
585
+ <TableRow>
586
+ <StyledTableCell style={{width:"50px"}}className="not-print">Items</StyledTableCell>
587
+ <StyledTableCell style={{width:"55px"}}>Cantidad</StyledTableCell>
588
+ <StyledTableCell style={{width:"60px"}}>Cod prod</StyledTableCell>
589
+ <StyledTableCell className="not-print">Nombre prod</StyledTableCell>
590
+ <StyledTableCell>Descripcion</StyledTableCell>
591
+ <StyledTableCell style={{width:"80px"}}align="right">P unitario</StyledTableCell>
592
+ {conDescuentoEnItems?<StyledTableCell style={{width:"40px"}}align="right">Dto</StyledTableCell>:null}
593
+ {conDescuentoEnItems?<StyledTableCell style={{width:"50px"}}align="right">Bonif</StyledTableCell>:null}
594
+ <StyledTableCell style={{width:"80px"}}align="right">Total</StyledTableCell>
595
+ </TableRow>
596
+ </TableHead>
597
+ <TableBody>
598
+ {items.map((item, index) => (
599
+ <ItemP
600
+ key={index}
601
+ index={index}
602
+ item={item}
603
+ editando={props.editando}
604
+ cliente={cliente}
605
+ conDescuentoEnItems={conDescuentoEnItems}
606
+ datosDesde={datosDesde}
607
+ />
608
+ ))}
609
+ <TableRow>
610
+ <StyledTableCell className="not-print">
611
+ <Button
612
+ size="small"
613
+ variant="outlined"
614
+ color="primary"
615
+ onClick={handleAdd}
616
+ disabled={!props.editando}
617
+ onKeyDown={(event)=>{
618
+ //enter
619
+ if([13].includes(event.keyCode)){
620
+ handleAdd()
621
+ }
622
+ }}
623
+ className={classes.button}
624
+ >
625
+ <ICON.Add/>
626
+ </Button>
627
+ </StyledTableCell>
628
+ <StyledTableCell className="not-print"></StyledTableCell>
629
+ {conDescuentoEnItems?<StyledTableCell colSpan={2}></StyledTableCell>:null}
630
+ {conOpciones?
631
+ <StyledTableCell colSpan={2} align="right"></StyledTableCell>
632
+ :
633
+ <>
634
+ <StyledTableCell colSpan={4} align="right">subtotal $</StyledTableCell>
635
+ <StyledTableCell align="right" style={totalsStyles}>{presupuestoOPedido.subtotal}</StyledTableCell>
636
+ </>
637
+ }
638
+ </TableRow>
639
+ {conOpciones?null:<>
640
+ <TableRow>
641
+ <StyledTableCell className="not-print"></StyledTableCell>
642
+ <StyledTableCell className="not-print"></StyledTableCell>
643
+ {conDescuentoEnItems?<StyledTableCell colSpan={2}></StyledTableCell>:null}
644
+ <StyledTableCell align="right" colSpan={4}>descuento general $</StyledTableCell>
645
+ <StyledTableCell align="right">
646
+ <TypedField
647
+ textAlign="right"
648
+ type="number"
649
+ disabled={!props.editando}
650
+ value={presupuestoOPedido.descuento}
651
+ id="descuento"
652
+ name="descuento"
653
+ onChange={
654
+ (value)=>
655
+ datosDesde=='datosPresupuesto'?
656
+ dispatch(dispatchers.SET_DESCUENTO_PRESUPUESTO({descuento:Number(value)}))
657
+ :
658
+ dispatch(dispatchers.SET_DESCUENTO_PEDIDO({descuento:Number(value)}))
659
+ }
660
+ />
661
+ </StyledTableCell>
662
+ </TableRow>
663
+ <TableRow>
664
+ <StyledTableCell className="not-print"></StyledTableCell>
665
+ <StyledTableCell className="not-print"></StyledTableCell>
666
+ {conDescuentoEnItems?<StyledTableCell colSpan={3}></StyledTableCell>:null}
667
+ <StyledTableCell align="right" colSpan={conDescuentoEnItems?3:4}>total $</StyledTableCell>
668
+ <StyledTableCell align="right" style={totalsStyles}>{presupuestoOPedido.total}</StyledTableCell>
669
+ </TableRow>
670
+ </>}
671
+ </TableBody>
672
+ </Table>
673
+ </TableContainer>
674
+ }
675
+
676
+ export function ItemP(props:{item:Item, index:number, editando:boolean, conDescuentoEnItems:boolean, cliente: Cliente, datosDesde:DatosDesde}){
677
+ const {productos} = useSelector((estado:State)=>estado.estructura);
678
+ const productosArr = likeAr(productos).array();
679
+ const dispatch = useDispatch();
680
+ const {item, index, conDescuentoEnItems, cliente, datosDesde}=props;
681
+ const classes = useStyles();
682
+ const EDICION_LIBRE = item.producto==0;
683
+ return <Grow in={true}>
684
+ <StyledTableRow key={item.producto}>
685
+ <StyledTableCell className="not-print">
686
+ <Button
687
+ id={"agregar_" + index.toString()}
688
+ size="small"
689
+ variant="outlined"
690
+ disabled={!props.editando}
691
+ color="secondary"
692
+ onClick={()=>{
693
+ dispatch(dispatchers.REMOVE_ITEM({index: index, datosDesde}))
694
+ }}
695
+ className={classes.button}
696
+ >
697
+ <ICON.DeleteOutline/>
698
+ </Button>
699
+ </StyledTableCell>
700
+ <StyledTableCell align="right">
701
+ <TypedField
702
+ id={"cantidad_" + index.toString()}
703
+ type="number"
704
+ value={item.cantidad || null}
705
+ name="cantidad"
706
+ disabled={!props.editando}
707
+ onChange={
708
+ (value)=>dispatch(dispatchers.SET_CANTIDAD_ITEM({index,cantidad:Number(value), datosDesde}))
709
+ }
710
+ />
711
+ </StyledTableCell>
712
+ <StyledTableCell align="right">
713
+ <TypedField
714
+ value={item.producto || null}
715
+ id={"poroducto_" + index.toString()}
716
+ name="producto"
717
+ type="number"
718
+ disabled={!props.editando}
719
+ matchInList={EDICION_LIBRE?undefined:productosArr.map((producto)=>producto.producto)}
720
+ onChange={(value)=>{
721
+ if(value=='0'){
722
+ dispatch(dispatchers.SET_FIELD_CUSTOM_ITEM({index, campo:'producto',valor:value, datosDesde}))
723
+ }else{
724
+ var searchResult=null;
725
+ if(value){
726
+ searchResult = productosArr.find((producto)=>producto.producto==value) || null
727
+ }
728
+ dispatch(dispatchers.SET_PRODUCTO_ITEM({index, producto:searchResult, datosDesde}))
729
+ }
730
+ }}
731
+ />
732
+ </StyledTableCell>
733
+ <StyledTableCell className="not-print">
734
+ <AltoImpresionStyle charsPerLine={conDescuentoEnItems?60:75} componentId={"nombre-producto_" + index.toString()} lineHeight={30} value={item?.nombre || ''}/>
735
+ {
736
+ React.createElement(EDICION_LIBRE?TypedField:Autocompleter, {
737
+ ...{
738
+ value:item?.nombre || '',
739
+ id:"nombre-producto_" + index.toString(),
740
+ name:"nombre-producto",
741
+ disabled:!props.editando,
742
+ multiline:true,
743
+ matchInListConditionCallback:(producto: Producto)=>
744
+ cliente.cliente == producto.cliente || producto.cliente == null
745
+
746
+ },...(EDICION_LIBRE?{
747
+ onChange:(value: string)=>
748
+ dispatch(dispatchers.SET_FIELD_CUSTOM_ITEM({index, campo:'nombre',valor:value, datosDesde}))
749
+ }:{
750
+ rows: productosArr,
751
+ searchKey:"nombre",
752
+ searchType:"text",
753
+ onSelection:
754
+ (productoSeleccionado:Producto|null)=>
755
+ dispatch(dispatchers.SET_PRODUCTO_ITEM({index, producto:productoSeleccionado, datosDesde}))
756
+ })
757
+ })
758
+ }
759
+ </StyledTableCell>
760
+ <StyledTableCell>
761
+ <AltoImpresionStyle charsPerLine={conDescuentoEnItems?60:75} componentId={"descripcion-producto_" + index.toString()} lineHeight={30} value={item?.descripcion || ''}/>
762
+ {
763
+ React.createElement(EDICION_LIBRE?TypedField:Autocompleter, {
764
+ ...{
765
+ value:item?.descripcion || '',
766
+ id:"descripcion-producto_" + index.toString(),
767
+ name:"descripcion-producto",
768
+ disabled:!props.editando,
769
+ multiline:true,
770
+ },...(EDICION_LIBRE?{
771
+ onChange:(value:string)=>
772
+ dispatch(dispatchers.SET_FIELD_CUSTOM_ITEM({index, campo:'descripcion',valor:value, datosDesde}))
773
+ }:{
774
+ rows: productosArr,
775
+ searchKey:"descripcion",
776
+ searchType:"text",
777
+ onSelection:
778
+ (productoSeleccionado:Producto|null)=>
779
+ dispatch(dispatchers.SET_PRODUCTO_ITEM({index, producto:productoSeleccionado, datosDesde}))
780
+ })
781
+ })
782
+ }
783
+ </StyledTableCell>
784
+ <StyledTableCell align="right">{
785
+ EDICION_LIBRE?
786
+ <TypedField
787
+ textAlign="right"
788
+ id={"precio_unitario" + index.toString()}
789
+ type="number"
790
+ value={item.precio_unitario || null}
791
+ name="precio_unitario"
792
+ disabled={!props.editando}
793
+ onChange={
794
+ (value)=>dispatch(dispatchers.SET_FIELD_CUSTOM_ITEM({index, campo:'precio_unitario',valor:value, datosDesde}))
795
+ }
796
+ />
797
+ :
798
+ item.precio_unitario?.toString()}
799
+ </StyledTableCell>
800
+ {conDescuentoEnItems?
801
+ <>
802
+ <StyledTableCell align="right">
803
+ <TypedField
804
+ textAlign="right"
805
+ value={item.porcentaje_descuento|| 0}
806
+ id={"porcentaje_descuento_" + index.toString()}
807
+ name="porcentaje_descuento"
808
+ type="number"
809
+ disabled={!props.editando}
810
+ onChange={(value)=>{
811
+ dispatch(dispatchers.SET_DESCUENTO_ITEM({index, porcentaje:Number(value), datosDesde}))
812
+ }}
813
+ inputAdornment="%"
814
+ />
815
+ </StyledTableCell>
816
+ <StyledTableCell align="right">
817
+ {item.descuento}
818
+ </StyledTableCell>
819
+ </>
820
+ :null}
821
+ <StyledTableCell align="right" style={totalsStyles}>{item.total?.toString()}</StyledTableCell>
822
+ </StyledTableRow>
823
+ </Grow>
824
+ }
825
+
826
+ const StyledTableCell = withStyles((theme: Theme) =>
827
+ createStyles({
828
+ head: {
829
+ backgroundColor: theme.palette.common.black,
830
+ color: theme.palette.common.white,
831
+ },
832
+ body: {
833
+ fontSize: 14,
834
+ padding: 8
835
+ },
836
+ }),
837
+ )(TableCell);
838
+
839
+ const StyledTableRow = withStyles((theme: Theme) =>
840
+ createStyles({
841
+ root: {
842
+ '&:nth-of-type(odd)': {
843
+ backgroundColor: theme.palette.action.hover,
844
+ },
845
+ },
846
+ }),
847
+ )(TableRow);
848
+
849
+ export function PresupuestoComponent(_props:{}){
850
+ const {cliente, presupuesto, items} = useSelector((estado:State)=>(estado.datosPresupuesto));
851
+ const {estadosPresupuesto} = useSelector((estado:State)=>({estadosPresupuesto:estado.estructura.estadosPresupuesto}));
852
+ const {parametros} = useSelector((estado:State)=>(estado.estructura));
853
+ const classes = useStyles();
854
+ const dispatch = useDispatch();
855
+ const [open, setOpen] = useState(false);
856
+ const esNuevo = !(presupuesto.presupuesto);
857
+ const disabled = !estadosPresupuesto.find((estado)=>estado.estado == presupuesto.estado)?.permite_editar;
858
+ const [editando, setEditando] = useState(esNuevo);
859
+ const [snackBarMessage, setSnackBarMessage] = useState('');
860
+ const hayDescuentosIngresados = items.some((item)=>item.descuento != 0);
861
+ const handleClose = (_event: React.SyntheticEvent | React.MouseEvent, reason?: string) => {
862
+ if (reason === 'clickaway') {
863
+ return;
864
+ }
865
+
866
+ setOpen(false);
867
+ };
868
+ return <main className={classes.layout} id="presupuesto">
869
+ <img id="banner-superior-impresion" className="only-for-print" src="img/bannersuperior.png" alt="bannersuperior.png"/>
870
+ <Grid container alignItems="flex-start" justify="flex-start" direction="row">
871
+ <h2>{presupuesto.presupuesto?`Presupuesto Nº ${presupuesto.presupuesto}`:`Nuevo presupuesto`}</h2>
872
+ </Grid>
873
+ <Grid container alignItems="flex-start" justify="flex-end" direction="row">
874
+ <Button
875
+ variant="contained"
876
+ color={editando?"secondary":"primary"}
877
+ onClick={()=>{
878
+ setEditando(!editando)
879
+ }}
880
+ disabled={disabled}
881
+ className={classes.button}
882
+ >
883
+ {editando?
884
+ <ICON.Cancel/>
885
+ :
886
+ <ICON.Edit/>
887
+ }
888
+ </Button>
889
+ <Button
890
+ variant="contained"
891
+ disabled={editando}
892
+ onClick={()=>{
893
+ window.print()
894
+ }}
895
+ className={classes.button}
896
+ >
897
+ <ICON.Print/>
898
+ </Button>
899
+ </Grid>
900
+ <ClienteComponent editando={editando} datosDesde="datosPresupuesto"/>
901
+ <EstadoPresupuesto editando={editando}/>
902
+ <Items
903
+ editando={editando}
904
+ conDescuentoEnItems={presupuesto.con_descuento_en_items}
905
+ conOpciones={presupuesto.con_opciones}
906
+ datosDesde='datosPresupuesto'
907
+ presupuestoOPedido={presupuesto}
908
+ />
909
+ <Grid container className="not-print" alignItems="flex-start" justify="flex-end" direction="row">
910
+ <FormControlLabel
911
+ control={
912
+ <Checkbox
913
+ checked={presupuesto.con_opciones}
914
+ onChange={
915
+ ((event: React.ChangeEvent<HTMLInputElement>)=>
916
+ dispatch(dispatchers.SET_CAMPO_PRESUPUESTO({campo:"con_opciones", valor:event.target.checked}))
917
+ )}
918
+ disabled={!editando}
919
+ name="con_opciones"
920
+ color="primary"
921
+ />
922
+ }
923
+ labelPlacement="start"
924
+ label="ocultar totales"
925
+ />
926
+ </Grid>
927
+ <Grid container className="not-print" alignItems="flex-start" justify="flex-end" direction="row">
928
+ <FormControlLabel
929
+ control={
930
+ <Checkbox
931
+ checked={presupuesto.con_descuento_en_items}
932
+ onChange={
933
+ ((event: React.ChangeEvent<HTMLInputElement>)=>
934
+ dispatch(dispatchers.SET_CAMPO_PRESUPUESTO({campo:"con_descuento_en_items", valor:event.target.checked}))
935
+ )}
936
+ disabled={!editando || hayDescuentosIngresados}
937
+ name="con_descuento_en_items"
938
+ color="primary"
939
+ />
940
+ }
941
+ labelPlacement="start"
942
+ label="descuento en items"
943
+ />
944
+ </Grid>
945
+ <Grid container alignItems="flex-start" justify="flex-end" direction="row">
946
+ <Button
947
+ variant="contained"
948
+ color="primary"
949
+ disabled={!editando}
950
+ onClick={async ()=>{
951
+ try{
952
+ let result = await guardarPresupuesto(cliente, presupuesto, items);
953
+ dispatch(dispatchers.SET_CAMPO_PRESUPUESTO({campo:"presupuesto", valor:result.presupuesto}))
954
+ setSnackBarMessage(`Presupuesto ${result.presupuesto} guardado`)
955
+ setOpen(true);
956
+ setEditando(false);
957
+ }catch(err){
958
+ setSnackBarMessage(`Error al guardar el presupuesto. ${err.message}`)
959
+ setOpen(true);
960
+ }
961
+ }}
962
+ className={classes.button}
963
+ >
964
+ <ICON.Save/>
965
+ </Button>
966
+ <Snackbar
967
+ anchorOrigin={{
968
+ vertical: 'bottom',
969
+ horizontal: 'left',
970
+ }}
971
+ open={open}
972
+ autoHideDuration={6000}
973
+ onClose={handleClose}
974
+ message={snackBarMessage}
975
+ action={
976
+ <React.Fragment>
977
+ <IconButton size="small" aria-label="close" color="inherit" onClick={handleClose}>
978
+ <ICON.Close/>
979
+ </IconButton>
980
+ </React.Fragment>
981
+ }
982
+ />
983
+ </Grid>
984
+ {parametros?.legales_presupuesto?
985
+ <Paper className={classes.paper+" legales"}>
986
+ <p>{parametros.legales_presupuesto}</p>
987
+ </Paper>
988
+ :
989
+ null
990
+ }
991
+ </main>
992
+ }
993
+
994
+ export function PedidoComponent(_props:{}){
995
+ const {cliente, pedido, items} = useSelector((estado:State)=>(estado.datosPedido));
996
+ const classes = useStyles();
997
+ const dispatch = useDispatch();
998
+ const [open, setOpen] = useState(false);
999
+ const esNuevo = !(pedido.pedido);
1000
+ const [editando, setEditando] = useState(esNuevo);
1001
+ const [snackBarMessage, setSnackBarMessage] = useState('');
1002
+ const handleClose = (_event: React.SyntheticEvent | React.MouseEvent, reason?: string) => {
1003
+ if (reason === 'clickaway') {
1004
+ return;
1005
+ }
1006
+
1007
+ setOpen(false);
1008
+ };
1009
+ return <main className={classes.layout} id="pedido">
1010
+ <img id="banner-superior-impresion" className="only-for-print" src="img/bannersuperior.png" alt="bannersuperior.png"/>
1011
+ <Grid container alignItems="flex-start" justify="flex-start" direction="row">
1012
+ <h2>{pedido.pedido?`Pedido Nº ${pedido.pedido}`:`Nuevo pedido`}</h2>
1013
+ </Grid>
1014
+ <Grid container alignItems="flex-start" justify="flex-end" direction="row">
1015
+ <Button
1016
+ variant="contained"
1017
+ color={editando?"secondary":"primary"}
1018
+ onClick={()=>{
1019
+ setEditando(!editando)
1020
+ }}
1021
+ className={classes.button}
1022
+ >
1023
+ {editando?
1024
+ <ICON.Cancel/>
1025
+ :
1026
+ <ICON.Edit/>
1027
+ }
1028
+ </Button>
1029
+ <Button
1030
+ variant="contained"
1031
+ disabled={editando}
1032
+ onClick={()=>{
1033
+ window.print()
1034
+ }}
1035
+ className={classes.button}
1036
+ >
1037
+ <ICON.Print/>
1038
+ </Button>
1039
+ </Grid>
1040
+ <ClienteComponent editando={editando} datosDesde="datosPedido"/>
1041
+ <EstadoPedido editando={editando}/>
1042
+ <Items
1043
+ editando={editando}
1044
+ conDescuentoEnItems={true}
1045
+ conOpciones={false}
1046
+ datosDesde='datosPedido'
1047
+ presupuestoOPedido={pedido}
1048
+ />
1049
+ <ObservacionesPedido editando={editando}/>
1050
+ <Grid container alignItems="flex-start" justify="flex-end" direction="row">
1051
+ <Button
1052
+ variant="contained"
1053
+ color="primary"
1054
+ disabled={!editando}
1055
+ onClick={async ()=>{
1056
+ try{
1057
+ let result = await guardarPedido(cliente, pedido, items);
1058
+ dispatch(dispatchers.SET_CAMPO_PEDIDO({campo:"pedido", valor:result.pedido}))
1059
+ setSnackBarMessage(`Pedido ${result.pedido} guardado`)
1060
+ setOpen(true);
1061
+ setEditando(false);
1062
+ }catch(err){
1063
+ setSnackBarMessage(`Error al guardar el pedido. ${err.message}`)
1064
+ setOpen(true);
1065
+ }
1066
+ }}
1067
+ className={classes.button}
1068
+ >
1069
+ <ICON.Save/>
1070
+ </Button>
1071
+ <Snackbar
1072
+ anchorOrigin={{
1073
+ vertical: 'bottom',
1074
+ horizontal: 'left',
1075
+ }}
1076
+ open={open}
1077
+ autoHideDuration={6000}
1078
+ onClose={handleClose}
1079
+ message={snackBarMessage}
1080
+ action={
1081
+ <React.Fragment>
1082
+ <IconButton size="small" aria-label="close" color="inherit" onClick={handleClose}>
1083
+ <ICON.Close/>
1084
+ </IconButton>
1085
+ </React.Fragment>
1086
+ }
1087
+ />
1088
+ </Grid>
1089
+ </main>
1090
+ }
1091
+
1092
+ export async function desplegarPresupuesto(store:Store<State,ActionFormularioState>){
1093
+ ReactDOM.render(
1094
+ <Provider store={store}>
1095
+ <PresupuestoComponent/>
1096
+ </Provider>
1097
+ ,
1098
+ document.getElementById('main_layout')
1099
+ )
1100
+ }
1101
+
1102
+ export async function desplegarPedido(store:Store<State,ActionFormularioState>){
1103
+ ReactDOM.render(
1104
+ <Provider store={store}>
1105
+ <PedidoComponent/>
1106
+ </Provider>
1107
+ ,
1108
+ document.getElementById('main_layout')
1109
+ )
1110
+ }
1111
+
1112
+ if(typeof window !== 'undefined'){
1113
+ // @ts-ignore para hacerlo
1114
+ window.desplegarPresupuesto = desplegarPresupuesto;
1115
+ // @ts-ignore para hacerlo
1116
+ window.desplegarPedido = desplegarPedido;
1117
+ }