operx-cli 0.9.0__tar.gz → 0.10.0__tar.gz
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.
- {operx_cli-0.9.0 → operx_cli-0.10.0}/PKG-INFO +5 -1
- {operx_cli-0.9.0 → operx_cli-0.10.0}/README.md +4 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/AGENTES.md +6 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/__init__.py +1 -1
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/campos.py +81 -32
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli.egg-info/PKG-INFO +5 -1
- {operx_cli-0.9.0 → operx_cli-0.10.0}/pyproject.toml +1 -1
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_campos.py +67 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/agent_guide.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/api_fields.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/app.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/browser_login.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/client.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/__init__.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/account.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/ambiental.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/articulos.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/avisos.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/chat.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/common.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/completar.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/empresas.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/inventario.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/lecturas.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/muestreo.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/ordenes.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/personas.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/planta.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/preventivos.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/raw.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/registros.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/reportes.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/rutinas.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/search.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/sst.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/commands/update.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/lookup.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/output.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/record_completion.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/self_update.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/settings.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/shell.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/spreadsheet.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/updates.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/values.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli/welcome.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli.egg-info/SOURCES.txt +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli.egg-info/dependency_links.txt +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli.egg-info/entry_points.txt +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli.egg-info/requires.txt +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/operx_cli.egg-info/top_level.txt +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/setup.cfg +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_agentes.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_api_campos.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_app.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_articulos.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_cards.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_chat.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_client.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_completar.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_daily_flows.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_inventario_lecturas.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_json_writes.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_login.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_logout_and_routes.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_lookup.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_more_of_the_api.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_muestreo.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_ordenes.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_output.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_personas.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_preventivos.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_record_completion.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_registros.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_rutinas.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_self_update.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_settings.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_shell.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_spreadsheet.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_sst_ambiental.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_updates.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_values.py +0 -0
- {operx_cli-0.9.0 → operx_cli-0.10.0}/tests/test_welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: operx-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: La línea de comandos de OperX: tu empresa desde la terminal, con tu token de API.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -184,6 +184,10 @@ operx campos listar # la información ex
|
|
|
184
184
|
operx campos crear "Hijos" --tipo numero
|
|
185
185
|
operx campos guardar "Ana Ruiz" --valor "Hijos=2" --valor "Fecha de nacimiento=17/05/1990"
|
|
186
186
|
operx campos ver "Ana Ruiz"
|
|
187
|
+
operx campos listar --de equipos # la información extra de los equipos
|
|
188
|
+
operx campos crear "Voltaje" --tipo numero --de equipos
|
|
189
|
+
operx campos guardar BOM-01 --valor "Voltaje=220" --de equipos
|
|
190
|
+
operx campos ver BOM-01 --de equipos
|
|
187
191
|
|
|
188
192
|
operx sst acciones --estado abiertas
|
|
189
193
|
operx sst nueva-accion --titulo "Señalizar la salida" --para 15/11/2026 --responsable "Ana Ruiz"
|
|
@@ -170,6 +170,10 @@ operx campos listar # la información ex
|
|
|
170
170
|
operx campos crear "Hijos" --tipo numero
|
|
171
171
|
operx campos guardar "Ana Ruiz" --valor "Hijos=2" --valor "Fecha de nacimiento=17/05/1990"
|
|
172
172
|
operx campos ver "Ana Ruiz"
|
|
173
|
+
operx campos listar --de equipos # la información extra de los equipos
|
|
174
|
+
operx campos crear "Voltaje" --tipo numero --de equipos
|
|
175
|
+
operx campos guardar BOM-01 --valor "Voltaje=220" --de equipos
|
|
176
|
+
operx campos ver BOM-01 --de equipos
|
|
173
177
|
|
|
174
178
|
operx sst acciones --estado abiertas
|
|
175
179
|
operx sst nueva-accion --titulo "Señalizar la salida" --para 15/11/2026 --responsable "Ana Ruiz"
|
|
@@ -110,6 +110,12 @@ llamada queda en el historial del token, marcada como hecha desde la CLI.
|
|
|
110
110
|
los campos que digas; `--vaciar Hijos` deja uno vacío) y `campos ver`. Antes
|
|
111
111
|
de crear un campo, mira que no exista ya. `campos quitar` lo oculta de todas
|
|
112
112
|
las personas y deja los valores guardados.
|
|
113
|
+
- Lo que la ficha de un equipo no tiene (voltaje, capacidad, última
|
|
114
|
+
inspección…) va igual, con `--de equipos` en cada comando: `campos listar
|
|
115
|
+
--de equipos`, `campos crear "Voltaje" --tipo numero --de equipos`, `campos
|
|
116
|
+
guardar BOM-01 --valor "Voltaje=220" --de equipos`. Personas y equipos tienen
|
|
117
|
+
campos aparte. Los clientes ven estos valores en su portal: no guardes ahí lo
|
|
118
|
+
que no deban leer.
|
|
113
119
|
- SG-SST y Ambiental van por empresa: sin `--empresa`, la de la cuenta. Una
|
|
114
120
|
cuenta que lleva el SG-SST o lo ambiental de sus clientes ve las demás con
|
|
115
121
|
`operx sst empresas` o `operx ambiental empresas`.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""`operx campos`: Información extra, the fields a company adds to the record
|
|
2
|
-
of its people (a birthday, the children, the marital status...)
|
|
3
|
-
|
|
4
|
-
(backend/extra_fields,
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
of its people (a birthday, the children, the marital status...) or of its
|
|
3
|
+
machines (the voltage, the last inspection...) for what the record doesn't
|
|
4
|
+
have, and their values on each one (backend/extra_fields,
|
|
5
|
+
backend/people/extra_info.py, backend/assets/extra_info.py). The fields are
|
|
6
|
+
company-wide; each record has a value or none for every one. `--de` says which
|
|
7
|
+
kind of record: people unless it says otherwise."""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
7
10
|
from decimal import Decimal
|
|
8
11
|
from enum import Enum
|
|
9
12
|
from typing import Annotated, Optional
|
|
@@ -16,12 +19,42 @@ from ..values import decimal_text, iso_date
|
|
|
16
19
|
from .common import CliError, CsvOption, JsonOption, YesOption, client_of, confirm_write, output_format, print_result
|
|
17
20
|
|
|
18
21
|
app = typer.Typer(
|
|
19
|
-
help="Información extra: los campos que tu empresa agrega a
|
|
22
|
+
help="Información extra: los campos que tu empresa agrega a personas y equipos, y sus valores.",
|
|
20
23
|
no_args_is_help=True,
|
|
21
24
|
)
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
|
|
27
|
+
class Registro(str, Enum):
|
|
28
|
+
personas = "personas"
|
|
29
|
+
equipos = "equipos"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class RecordKind:
|
|
34
|
+
"""How a kind of record is found and where its values are."""
|
|
35
|
+
|
|
36
|
+
# A key of backend/extra_fields/entities.py.
|
|
37
|
+
entity: str
|
|
38
|
+
everyone: str
|
|
39
|
+
list_path: str
|
|
40
|
+
lookup_fields: tuple
|
|
41
|
+
what: str
|
|
42
|
+
name_field: str
|
|
43
|
+
values_path: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
KINDS = {
|
|
47
|
+
Registro.personas: RecordKind(
|
|
48
|
+
entity="employee", everyone="todas las personas", list_path="employees",
|
|
49
|
+
lookup_fields=("user_name", "employee_code"), what="persona", name_field="user_name",
|
|
50
|
+
values_path="people/employees/{id}/extra-fields",
|
|
51
|
+
),
|
|
52
|
+
Registro.equipos: RecordKind(
|
|
53
|
+
entity="equipment", everyone="todos los equipos", list_path="assets",
|
|
54
|
+
lookup_fields=("code", "name"), what="equipo", name_field="name",
|
|
55
|
+
values_path="assets/{id}/extra-fields",
|
|
56
|
+
),
|
|
57
|
+
}
|
|
25
58
|
|
|
26
59
|
FIELD_COLUMNS = [("id", "Id"), ("name", "Campo"), ("type", "Tipo"), ("options", "Opciones")]
|
|
27
60
|
VALUE_COLUMNS = [("name", "Campo"), ("value", "Valor")]
|
|
@@ -50,10 +83,14 @@ OpcionOption = Annotated[
|
|
|
50
83
|
Optional[list[str]],
|
|
51
84
|
typer.Option("--opcion", help="Una opción de la lista; repítela para varias. Solo para el tipo lista."),
|
|
52
85
|
]
|
|
86
|
+
DeOption = Annotated[Registro, typer.Option("--de", help="De qué registro son los campos: personas o equipos.")]
|
|
87
|
+
RegistroArgument = Annotated[
|
|
88
|
+
str, typer.Argument(help="El nombre exacto, el código o el id de la persona o del equipo."),
|
|
89
|
+
]
|
|
53
90
|
|
|
54
91
|
|
|
55
|
-
def _fields(client):
|
|
56
|
-
return client.get("extra-fields", {"entity":
|
|
92
|
+
def _fields(client, kind):
|
|
93
|
+
return client.get("extra-fields", {"entity": kind.entity})
|
|
57
94
|
|
|
58
95
|
|
|
59
96
|
def _find_field(fields, text):
|
|
@@ -64,8 +101,8 @@ def _find_field(fields, text):
|
|
|
64
101
|
raise CliError(str(error)) from None
|
|
65
102
|
|
|
66
103
|
|
|
67
|
-
def
|
|
68
|
-
return find_one(client,
|
|
104
|
+
def _record(client, kind, text):
|
|
105
|
+
return find_one(client, kind.list_path, text, list(kind.lookup_fields), kind.what)
|
|
69
106
|
|
|
70
107
|
|
|
71
108
|
def _split_pair(pair):
|
|
@@ -123,12 +160,13 @@ def _shown(field):
|
|
|
123
160
|
@app.command()
|
|
124
161
|
def listar(
|
|
125
162
|
ctx: typer.Context,
|
|
163
|
+
de: DeOption = Registro.personas,
|
|
126
164
|
as_json: JsonOption = False,
|
|
127
165
|
as_csv: CsvOption = False,
|
|
128
166
|
):
|
|
129
|
-
"""Los campos de información extra de las personas: su nombre, su tipo y las opciones de las listas."""
|
|
167
|
+
"""Los campos de información extra de las personas o de los equipos: su nombre, su tipo y las opciones de las listas."""
|
|
130
168
|
output = output_format(as_json, as_csv)
|
|
131
|
-
fields = _fields(client_of(ctx))
|
|
169
|
+
fields = _fields(client_of(ctx), KINDS[de])
|
|
132
170
|
if output != "json":
|
|
133
171
|
fields = [
|
|
134
172
|
{**field, "type": TYPE_LABELS.get(field["field_type"], field["field_type"]), "options": ", ".join(field["options"])}
|
|
@@ -143,16 +181,18 @@ def crear(
|
|
|
143
181
|
nombre: Annotated[str, typer.Argument(help="El nombre del campo: «Fecha de nacimiento».")],
|
|
144
182
|
tipo: Annotated[Tipo, typer.Option("--tipo", help="Qué guarda el campo; no se cambia después.")] = Tipo.texto,
|
|
145
183
|
opcion: OpcionOption = None,
|
|
184
|
+
de: DeOption = Registro.personas,
|
|
146
185
|
si: YesOption = False,
|
|
147
186
|
as_json: JsonOption = False,
|
|
148
187
|
):
|
|
149
|
-
"""Agrega un campo a la ficha de las personas
|
|
188
|
+
"""Agrega un campo a la ficha de las personas o de los equipos. Todos lo tienen, vacío hasta que se llene."""
|
|
150
189
|
if tipo != Tipo.lista and opcion:
|
|
151
190
|
raise CliError("--opcion solo va con --tipo lista.")
|
|
152
191
|
if tipo == Tipo.lista and not opcion:
|
|
153
192
|
raise CliError("Una lista necesita sus opciones: --opcion «Soltero/a» --opcion «Casado/a».")
|
|
154
|
-
|
|
155
|
-
|
|
193
|
+
kind = KINDS[de]
|
|
194
|
+
body = {"entity": kind.entity, "name": nombre, "field_type": TYPE_KEYS[tipo.value], "options": opcion or []}
|
|
195
|
+
confirm_write(f"Agregar a {kind.everyone} el campo «{nombre}» ({TYPE_LABELS[body['field_type']]}).", si)
|
|
156
196
|
created = client_of(ctx).send("POST", "extra-fields", json=body).json()
|
|
157
197
|
print_result(created, as_json, f"Campo {created['id']} creado: {created['name']}.")
|
|
158
198
|
|
|
@@ -163,6 +203,7 @@ def cambiar(
|
|
|
163
203
|
campo: Annotated[str, typer.Argument(help="El id o el nombre exacto del campo.")],
|
|
164
204
|
nombre: Annotated[Optional[str], typer.Option("--nombre", help="El nombre nuevo.")] = None,
|
|
165
205
|
opcion: OpcionOption = None,
|
|
206
|
+
de: DeOption = Registro.personas,
|
|
166
207
|
si: YesOption = False,
|
|
167
208
|
as_json: JsonOption = False,
|
|
168
209
|
):
|
|
@@ -170,7 +211,7 @@ def cambiar(
|
|
|
170
211
|
if not (nombre or opcion):
|
|
171
212
|
raise CliError("Di qué cambiar: --nombre o --opcion.")
|
|
172
213
|
client = client_of(ctx)
|
|
173
|
-
found = _find_field(_fields(client), campo)
|
|
214
|
+
found = _find_field(_fields(client, KINDS[de]), campo)
|
|
174
215
|
body = {}
|
|
175
216
|
if nombre:
|
|
176
217
|
body["name"] = nombre
|
|
@@ -185,29 +226,35 @@ def cambiar(
|
|
|
185
226
|
def quitar(
|
|
186
227
|
ctx: typer.Context,
|
|
187
228
|
campo: Annotated[str, typer.Argument(help="El id o el nombre exacto del campo.")],
|
|
229
|
+
de: DeOption = Registro.personas,
|
|
188
230
|
si: YesOption = False,
|
|
189
231
|
as_json: JsonOption = False,
|
|
190
232
|
):
|
|
191
|
-
"""Quita un campo de la ficha de todas las personas. Los valores ya guardados quedan en la base de datos."""
|
|
233
|
+
"""Quita un campo de la ficha de todas las personas o todos los equipos. Los valores ya guardados quedan en la base de datos."""
|
|
192
234
|
client = client_of(ctx)
|
|
193
|
-
|
|
194
|
-
|
|
235
|
+
kind = KINDS[de]
|
|
236
|
+
found = _find_field(_fields(client, kind), campo)
|
|
237
|
+
confirm_write(f"Quitar el campo «{found['name']}» de {kind.everyone}.", si)
|
|
195
238
|
client.send("DELETE", f"extra-fields/{found['id']}")
|
|
196
|
-
print_result(
|
|
239
|
+
print_result(
|
|
240
|
+
{"id": found["id"], "removed": True}, as_json, f"Campo {found['id']} quitado de {kind.everyone}: {found['name']}.",
|
|
241
|
+
)
|
|
197
242
|
|
|
198
243
|
|
|
199
244
|
@app.command()
|
|
200
245
|
def ver(
|
|
201
246
|
ctx: typer.Context,
|
|
202
|
-
|
|
247
|
+
registro: RegistroArgument,
|
|
248
|
+
de: DeOption = Registro.personas,
|
|
203
249
|
as_json: JsonOption = False,
|
|
204
250
|
as_csv: CsvOption = False,
|
|
205
251
|
):
|
|
206
|
-
"""La información extra de una persona: cada campo con su valor."""
|
|
252
|
+
"""La información extra de una persona o de un equipo: cada campo con su valor."""
|
|
207
253
|
output = output_format(as_json, as_csv)
|
|
208
254
|
client = client_of(ctx)
|
|
209
|
-
|
|
210
|
-
|
|
255
|
+
kind = KINDS[de]
|
|
256
|
+
found = _record(client, kind, registro)
|
|
257
|
+
answer = client.get(kind.values_path.format(id=found["id"]))
|
|
211
258
|
if output == "json":
|
|
212
259
|
print_record(answer, "json")
|
|
213
260
|
return
|
|
@@ -218,7 +265,7 @@ def ver(
|
|
|
218
265
|
@app.command()
|
|
219
266
|
def guardar(
|
|
220
267
|
ctx: typer.Context,
|
|
221
|
-
|
|
268
|
+
registro: RegistroArgument,
|
|
222
269
|
valor: Annotated[
|
|
223
270
|
Optional[list[str]],
|
|
224
271
|
typer.Option("--valor", help="Campo=valor, por ejemplo «Hijos=2» o «Fecha de nacimiento=17/05/1990»; repítelo para varios."),
|
|
@@ -226,14 +273,16 @@ def guardar(
|
|
|
226
273
|
vaciar: Annotated[
|
|
227
274
|
Optional[list[str]], typer.Option("--vaciar", help="El campo que se deja vacío; repítelo para varios."),
|
|
228
275
|
] = None,
|
|
276
|
+
de: DeOption = Registro.personas,
|
|
229
277
|
si: YesOption = False,
|
|
230
278
|
as_json: JsonOption = False,
|
|
231
279
|
):
|
|
232
|
-
"""Guarda valores en la información extra de una persona. Solo cambian los campos que digas."""
|
|
280
|
+
"""Guarda valores en la información extra de una persona o de un equipo. Solo cambian los campos que digas."""
|
|
233
281
|
if not (valor or vaciar):
|
|
234
282
|
raise CliError("Di qué guardar: --valor Campo=valor o --vaciar Campo.")
|
|
235
283
|
client = client_of(ctx)
|
|
236
|
-
|
|
284
|
+
kind = KINDS[de]
|
|
285
|
+
fields = _fields(client, kind)
|
|
237
286
|
values = {}
|
|
238
287
|
for pair in valor or []:
|
|
239
288
|
name, text = _split_pair(pair)
|
|
@@ -241,9 +290,9 @@ def guardar(
|
|
|
241
290
|
values[str(field["id"])] = _value_for(field, text)
|
|
242
291
|
for name in vaciar or []:
|
|
243
292
|
values[str(_find_field(fields, name)["id"])] = None
|
|
244
|
-
|
|
245
|
-
who =
|
|
293
|
+
found = _record(client, kind, registro)
|
|
294
|
+
who = found.get(kind.name_field) or registro
|
|
246
295
|
changes = list(valor or []) + [f"vaciar {name}" for name in vaciar or []]
|
|
247
296
|
confirm_write(f"Guardar en la información extra de {who}: {'; '.join(changes)}.", si)
|
|
248
|
-
saved = client.send("PATCH",
|
|
297
|
+
saved = client.send("PATCH", kind.values_path.format(id=found["id"]), json={"values": values}).json()
|
|
249
298
|
print_result(saved, as_json, f"Información extra de {who} guardada.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: operx-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: La línea de comandos de OperX: tu empresa desde la terminal, con tu token de API.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -184,6 +184,10 @@ operx campos listar # la información ex
|
|
|
184
184
|
operx campos crear "Hijos" --tipo numero
|
|
185
185
|
operx campos guardar "Ana Ruiz" --valor "Hijos=2" --valor "Fecha de nacimiento=17/05/1990"
|
|
186
186
|
operx campos ver "Ana Ruiz"
|
|
187
|
+
operx campos listar --de equipos # la información extra de los equipos
|
|
188
|
+
operx campos crear "Voltaje" --tipo numero --de equipos
|
|
189
|
+
operx campos guardar BOM-01 --valor "Voltaje=220" --de equipos
|
|
190
|
+
operx campos ver BOM-01 --de equipos
|
|
187
191
|
|
|
188
192
|
operx sst acciones --estado abiertas
|
|
189
193
|
operx sst nueva-accion --titulo "Señalizar la salida" --para 15/11/2026 --responsable "Ana Ruiz"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "operx-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.0"
|
|
8
8
|
description = "La línea de comandos de OperX: tu empresa desde la terminal, con tu token de API."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -163,3 +163,70 @@ def test_guardar_with_nothing_to_save_says_so():
|
|
|
163
163
|
fake = FakeApi({})
|
|
164
164
|
result = run(fake, "campos", "guardar", "María Torres", "--si")
|
|
165
165
|
assert result.exit_code == 1 and "Di qué guardar" in result.output
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# --de equipos: the same commands on a machine's record.
|
|
169
|
+
# extra_fields/serializers.py ExtraFieldSerializer
|
|
170
|
+
VOLTAJE = {"id": 11, "entity": "equipment", "name": "Voltaje", "field_type": "number", "options": [], "position": 0}
|
|
171
|
+
MACHINE_FIELDS = [VOLTAJE]
|
|
172
|
+
# assets/serializers.py AssetListSerializer
|
|
173
|
+
BOMBA = {"id": 40, "name": "Bomba 1", "code": "BOM-01"}
|
|
174
|
+
# assets/extra_info.py extra_payload
|
|
175
|
+
BOMBA_VALUES = {"fields": [{"id": 11, "name": "Voltaje", "field_type": "number", "options": [], "value": 220}]}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_listar_de_equipos_asks_for_the_fields_of_the_machines():
|
|
179
|
+
fake = FakeApi({("GET", "extra-fields/"): MACHINE_FIELDS})
|
|
180
|
+
result = run(fake, "campos", "listar", "--de", "equipos")
|
|
181
|
+
assert result.exit_code == 0, result.output
|
|
182
|
+
assert "Voltaje" in result.output
|
|
183
|
+
assert fake.requests[0].url.params["entity"] == "equipment"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_crear_de_equipos_adds_the_field_to_the_machines():
|
|
187
|
+
fake = FakeApi({("POST", "extra-fields/"): (201, VOLTAJE)})
|
|
188
|
+
result = run(fake, "campos", "crear", "Voltaje", "--tipo", "numero", "--de", "equipos", "--si")
|
|
189
|
+
assert result.exit_code == 0, result.output
|
|
190
|
+
assert fake.sent("POST", "extra-fields/") == {"entity": "equipment", "name": "Voltaje", "field_type": "number", "options": []}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_cambiar_de_equipos_finds_the_field_among_those_of_the_machines():
|
|
194
|
+
fake = FakeApi({("GET", "extra-fields/"): MACHINE_FIELDS, ("PATCH", "extra-fields/11/"): VOLTAJE})
|
|
195
|
+
result = run(fake, "campos", "cambiar", "Voltaje", "--nombre", "Voltaje nominal", "--de", "equipos", "--si")
|
|
196
|
+
assert result.exit_code == 0, result.output
|
|
197
|
+
assert fake.requests[0].url.params["entity"] == "equipment"
|
|
198
|
+
assert fake.sent("PATCH", "extra-fields/11/") == {"name": "Voltaje nominal"}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_quitar_de_equipos_says_it_leaves_every_machine():
|
|
202
|
+
fake = FakeApi({("GET", "extra-fields/"): MACHINE_FIELDS, ("DELETE", "extra-fields/11/"): (204, None)})
|
|
203
|
+
result = run(fake, "campos", "quitar", "Voltaje", "--de", "equipos", "--si")
|
|
204
|
+
assert result.exit_code == 0, result.output
|
|
205
|
+
assert "todos los equipos" in result.output
|
|
206
|
+
assert fake.called("DELETE", "extra-fields/11/")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_ver_de_equipos_reads_the_machine_by_its_code():
|
|
210
|
+
fake = FakeApi({("GET", "assets/"): page(BOMBA), ("GET", "assets/40/extra-fields/"): BOMBA_VALUES})
|
|
211
|
+
result = run(fake, "campos", "ver", "BOM-01", "--de", "equipos")
|
|
212
|
+
assert result.exit_code == 0, result.output
|
|
213
|
+
assert "Voltaje" in result.output and "220" in result.output
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_guardar_de_equipos_saves_on_the_machine():
|
|
217
|
+
fake = FakeApi({
|
|
218
|
+
("GET", "extra-fields/"): MACHINE_FIELDS,
|
|
219
|
+
("GET", "assets/"): page(BOMBA),
|
|
220
|
+
("PATCH", "assets/40/extra-fields/"): BOMBA_VALUES,
|
|
221
|
+
})
|
|
222
|
+
result = run(fake, "campos", "guardar", "Bomba 1", "--valor", "Voltaje=220", "--de", "equipos", "--si")
|
|
223
|
+
assert result.exit_code == 0, result.output
|
|
224
|
+
assert "Bomba 1" in result.output
|
|
225
|
+
assert fake.sent("PATCH", "assets/40/extra-fields/") == {"values": {"11": 220}}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def test_de_refuses_a_kind_of_record_that_has_no_fields():
|
|
229
|
+
fake = FakeApi({})
|
|
230
|
+
result = run(fake, "campos", "listar", "--de", "ordenes")
|
|
231
|
+
assert result.exit_code != 0
|
|
232
|
+
assert fake.requests == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|