chore: remove failed one-shot patch workflow
This commit is contained in:
@@ -1,276 +0,0 @@
|
||||
name: One-shot mobile Acta/Hallazgo flow patch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- fix/mobile-act-finding-flow
|
||||
paths:
|
||||
- .github/workflows/_oneshot-mobile-act-finding-flow.yml
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
patch:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Apply coherent field-flow patch
|
||||
shell: bash
|
||||
run: |
|
||||
python3 <<'PY'
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
def replace_once(path: str, old: str, new: str):
|
||||
p = Path(path)
|
||||
text = p.read_text()
|
||||
count = text.count(old)
|
||||
if count != 1:
|
||||
raise SystemExit(f"{path}: expected 1 occurrence, found {count}: {old[:100]!r}")
|
||||
p.write_text(text.replace(old, new, 1))
|
||||
|
||||
def regex_once(path: str, pattern: str, repl: str, flags=0):
|
||||
p = Path(path)
|
||||
text = p.read_text()
|
||||
new, count = re.subn(pattern, repl, text, count=1, flags=flags)
|
||||
if count != 1:
|
||||
raise SystemExit(f"{path}: regex expected 1 occurrence, found {count}: {pattern[:100]!r}")
|
||||
p.write_text(new)
|
||||
|
||||
# 1) Fix the frozen-Yacimiento parent bug that leaves field creation with no types.
|
||||
replace_once(
|
||||
'api-v3/src/inspection-visits/f3-field-inventory-structure.service.ts',
|
||||
' const effectiveParentId = parentId ?? base.context.area.id;\n',
|
||||
' const effectiveParentId = parentId ?? base.parent.id;\n',
|
||||
)
|
||||
|
||||
# 2) An Acta can be created before any Inventory item is chosen.
|
||||
replace_once(
|
||||
'api-v3/src/inspection-acts/dto/create-inspection-act.dto.ts',
|
||||
' ArrayMinSize,\n',
|
||||
'',
|
||||
)
|
||||
replace_once(
|
||||
'api-v3/src/inspection-acts/dto/create-inspection-act.dto.ts',
|
||||
' @ArrayMinSize(1)\n',
|
||||
'',
|
||||
)
|
||||
replace_once(
|
||||
'api-v3/src/inspection-acts/inspection-acts.service.ts',
|
||||
" ): Promise<void> {\n const [row] = (await manager.query(`\n SELECT COUNT(*)::integer AS count\n FROM inspection_visit_assets\n WHERE visit_id = $1\n AND asset_id = ANY($2::uuid[])\n AND included = true\n `, [visitId, assetIds])) as Array<{ count: number }>;\n if (assetIds.length < 1 || Number(row?.count ?? 0) !== assetIds.length) {\n",
|
||||
" ): Promise<void> {\n if (assetIds.length === 0) return;\n const [row] = (await manager.query(`\n SELECT COUNT(*)::integer AS count\n FROM inspection_visit_assets\n WHERE visit_id = $1\n AND asset_id = ANY($2::uuid[])\n AND included = true\n `, [visitId, assetIds])) as Array<{ count: number }>;\n if (Number(row?.count ?? 0) !== assetIds.length) {\n",
|
||||
)
|
||||
|
||||
# 3) Android Acta creation no longer depends on a preselected asset.
|
||||
replace_once(
|
||||
'android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt',
|
||||
' assetId: String,\n visitCode: String,\n',
|
||||
' assetId: String? = null,\n visitCode: String,\n',
|
||||
)
|
||||
replace_once(
|
||||
'android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt',
|
||||
' assetIds = listOf(assetId),\n',
|
||||
' assetIds = listOfNotNull(assetId),\n',
|
||||
)
|
||||
|
||||
vm = 'android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt'
|
||||
replace_once(
|
||||
vm,
|
||||
' if (asset == null) {\n error = "Seleccioná primero una Instalación o Subinstalación para iniciar el Acta."\n return\n }\n',
|
||||
'',
|
||||
)
|
||||
replace_once(
|
||||
vm,
|
||||
' asset.id,\n currentVisit.code,\n',
|
||||
' asset?.id,\n currentVisit.code,\n',
|
||||
)
|
||||
replace_once(
|
||||
vm,
|
||||
' if (selectedFieldAsset?.capture?.readyForFinding == true) {\n loadFindingOptionsInternal(currentVisit.id, asset.id, created.id)\n }\n }\n }\n\n fun searchInventory',
|
||||
' if (asset != null && selectedFieldAsset?.capture?.readyForFinding == true) {\n loadFindingOptionsInternal(currentVisit.id, asset.id, created.id)\n }\n }\n }\n\n fun createAct(urgency: String = "NON_URGENT") = createActForSelectedInventory(urgency)\n\n fun searchInventory',
|
||||
)
|
||||
replace_once(
|
||||
vm,
|
||||
' launchBusy {\n val effectiveParentId = parentId ?: inventoryParentId ?: currentVisit.scopeAsset?.id ?: currentVisit.operationalArea?.id\n inventory = repository.fieldInventory(currentVisit.id, search, effectiveParentId).data\n }\n',
|
||||
' launchBusy {\n inventory = repository.fieldInventory(currentVisit.id, search, parentId).data\n }\n',
|
||||
)
|
||||
|
||||
# 4) Start -> Acta, then Acta -> Hallazgo. Inventory is an implementation detail, not the navigation model.
|
||||
visit_ui = 'android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt'
|
||||
replace_once(visit_ui, ' onClick = { model.startVisit() },\n', ' onClick = { model.startVisit(); onActs() },\n')
|
||||
replace_once(visit_ui, ' Text("Inventario de campo", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)\n', ' Text("Nuevo Hallazgo", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)\n')
|
||||
replace_once(visit_ui, ' Text("Área → Yacimiento → Instalación → Subinstalación", style = MaterialTheme.typography.bodySmall)\n', ' Text("Elegí una Instalación o Subinstalación", style = MaterialTheme.typography.bodySmall)\n')
|
||||
replace_once(visit_ui, ' var parentLabel by rememberSaveable(visit.id) { mutableStateOf("Área de la inspección") }\n', ' var parentLabel by rememberSaveable(visit.id) { mutableStateOf(visit.scopeAsset?.name ?: "Yacimiento de la inspección") }\n')
|
||||
replace_once(visit_ui, ' Text("Alta en campo", fontWeight = FontWeight.Bold)\n', ' Text("¿No está en el inventario?", fontWeight = FontWeight.Bold)\n')
|
||||
replace_once(visit_ui, ' Text("Padre: $parentLabel", style = MaterialTheme.typography.bodySmall)\n', ' Text("Ubicación: $parentLabel", style = MaterialTheme.typography.bodySmall)\n')
|
||||
replace_once(visit_ui, ' }) { Text(if (showCreate) "Ocultar" else "Agregar") }\n', ' }) { Text(if (showCreate) "Cancelar" else "+ Agregar") }\n')
|
||||
replace_once(visit_ui, ' parentLabel = "Área de la inspección"\n', ' parentLabel = visit.scopeAsset?.name ?: "Yacimiento de la inspección"\n')
|
||||
replace_once(visit_ui, ' }) { Text("Volver al Área") }\n', ' }) { Text("Volver al Yacimiento") }\n')
|
||||
replace_once(visit_ui, ' Text("Vas a crear", style = MaterialTheme.typography.bodySmall)\n', ' Text("Tipo de registro", style = MaterialTheme.typography.bodySmall)\n')
|
||||
replace_once(visit_ui, ' Text("Estructura disponible", modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), fontWeight = FontWeight.Bold)\n', ' Text("Instalaciones y subinstalaciones", modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), fontWeight = FontWeight.Bold)\n')
|
||||
replace_once(
|
||||
visit_ui,
|
||||
' items(model.inventory, key = { it.id }) { item ->\n',
|
||||
' items(\n model.inventory.filter { candidate ->\n candidate.type?.let(::f3TypeCode) in setOf("instalacion", "subinstalacion")\n },\n key = { it.id },\n ) { item ->\n',
|
||||
)
|
||||
replace_once(visit_ui, ' val childLabel = if (typeCode == "yacimiento") "Agregar instalación aquí" else "Agregar subinstalación aquí"\n', ' val childLabel = if (typeCode == "yacimiento") "+ Agregar instalación" else "+ Agregar subinstalación"\n')
|
||||
replace_once(visit_ui, ' Text("Abrir Inventario de campo")\n', ' Text("Inventario / Hallazgos")\n')
|
||||
|
||||
acts_ui = 'android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt'
|
||||
old_block = ''' val selectedInventory = model.selectedFieldAsset?.asset
|
||||
if (selectedInventory == null) {
|
||||
Text("Primero elegí una Instalación o Subinstalación desde Inventario de campo. Ese registro será el primer elemento del Acta.")
|
||||
Button(onClick = onGoInventory, modifier = Modifier.fillMaxWidth()) {
|
||||
Text("Ir a Inventario y elegir")
|
||||
}
|
||||
} else {
|
||||
Text("Inventario inicial: ${selectedInventory.name} · ${selectedInventory.code}")
|
||||
Button(
|
||||
onClick = { model.createActForSelectedInventory(newActUrgency) },
|
||||
enabled = !model.busy,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("Crear nueva Acta") }
|
||||
}
|
||||
'''
|
||||
new_block = ''' Text(
|
||||
"Primero abrí el Acta. Después, desde cada Hallazgo, elegís la Instalación o Subinstalación correspondiente.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
Button(
|
||||
onClick = { model.createAct(newActUrgency) },
|
||||
enabled = !model.busy,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("Crear nueva Acta") }
|
||||
'''
|
||||
replace_once(acts_ui, old_block, new_block)
|
||||
replace_once(acts_ui, ' Text("2. Hallazgos / verificaciones", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)\n', ' Text("2. Hallazgos", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)\n')
|
||||
replace_once(
|
||||
acts_ui,
|
||||
' Text("Los Hallazgos se cargan desde Inventario y quedan vinculados explícitamente a ${selected.code}. El Acta también puede finalizar sin Hallazgos cuando corresponde dejar constancia de una inspección o verificación sin nuevos incumplimientos.")\n Button(onClick = onGoInventory, modifier = Modifier.fillMaxWidth()) { Text("Ir a Inventario / Hallazgos") }\n',
|
||||
' Text("Cada Hallazgo se vincula a una Instalación o Subinstalación. Podés buscar una existente o agregarla en campo sin salir del Acta.")\n Button(onClick = onGoInventory, modifier = Modifier.fillMaxWidth()) { Text("+ Agregar Hallazgo") }\n',
|
||||
)
|
||||
|
||||
# 5) Version the Android UX cut.
|
||||
gradle = 'android-app/app/build.gradle.kts'
|
||||
replace_once(gradle, ' versionCode = 24\n', ' versionCode = 25\n')
|
||||
replace_once(gradle, ' versionName = "0.15.2"\n', ' versionName = "0.16.0"\n')
|
||||
|
||||
# 6) Shared field attributes for every Installation/Subinstallation structural type.
|
||||
migration = Path('api-v3/src/database/migrations/1790099100000-f6-3-mobile-field-common-attributes.ts')
|
||||
migration.write_text("""import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class F63MobileFieldCommonAttributes1790099100000 implements MigrationInterface {
|
||||
name = 'F63MobileFieldCommonAttributes1790099100000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
WITH target_types AS (
|
||||
SELECT id
|
||||
FROM asset_types
|
||||
WHERE lower(code) IN ('instalacion','subinstalacion')
|
||||
), fields(code,name,sort_order) AS (
|
||||
VALUES
|
||||
('campo_marca','Marca',10),
|
||||
('campo_modelo','Modelo',20),
|
||||
('campo_capacidad','Capacidad',30),
|
||||
('campo_numero_serie','Número de serie',40),
|
||||
('campo_funcion','Función',50)
|
||||
)
|
||||
INSERT INTO asset_attribute_definitions (
|
||||
asset_type_id, code, name, data_type, is_required, is_active, sort_order
|
||||
)
|
||||
SELECT target.id, fields.code, fields.name, 'TEXT'::asset_attribute_data_type,
|
||||
false, true, fields.sort_order
|
||||
FROM target_types target
|
||||
CROSS JOIN fields
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM asset_attribute_definitions existing
|
||||
WHERE existing.asset_type_id=target.id
|
||||
AND lower(existing.code)=lower(fields.code)
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
DELETE FROM asset_attribute_definitions definition
|
||||
USING asset_types type
|
||||
WHERE definition.asset_type_id=type.id
|
||||
AND lower(type.code) IN ('instalacion','subinstalacion')
|
||||
AND definition.code IN (
|
||||
'campo_marca','campo_modelo','campo_capacidad','campo_numero_serie','campo_funcion'
|
||||
)
|
||||
`);
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
# 7) Contract tests for the corrected workflow.
|
||||
test_file = Path('api-v3/test/unit/f6-3-mobile-act-finding-flow.test.ts')
|
||||
test_file.write_text("""import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
function source(path: string) {
|
||||
return readFileSync(resolve(process.cwd(), path), 'utf8');
|
||||
}
|
||||
|
||||
test('F6.3 field types start at the frozen Yacimiento instead of the Area', () => {
|
||||
const structure = source('src/inspection-visits/f3-field-inventory-structure.service.ts');
|
||||
assert.match(structure, /parentId \?\? base\.parent\.id/);
|
||||
assert.doesNotMatch(structure, /parentId \?\? base\.context\.area\.id/);
|
||||
});
|
||||
|
||||
test('F6.3 an Acta can start empty and receive Inventory when Hallazgos are added', () => {
|
||||
const dto = source('src/inspection-acts/dto/create-inspection-act.dto.ts');
|
||||
const service = source('src/inspection-acts/inspection-acts.service.ts');
|
||||
const mobile = source('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/data/MobileActs.kt');
|
||||
assert.doesNotMatch(dto, /ArrayMinSize\(1\)/);
|
||||
assert.match(service, /if \(assetIds\.length === 0\) return/);
|
||||
assert.match(mobile, /assetIds = listOfNotNull\(assetId\)/);
|
||||
});
|
||||
|
||||
test('F6.3 every Installation and Subinstallation receives the common field card', () => {
|
||||
const migration = source('src/database/migrations/1790099100000-f6-3-mobile-field-common-attributes.ts');
|
||||
for (const label of ['Marca', 'Modelo', 'Capacidad', 'Número de serie', 'Función']) {
|
||||
assert.match(migration, new RegExp(label));
|
||||
}
|
||||
assert.match(migration, /'instalacion','subinstalacion'/);
|
||||
assert.match(migration, /'TEXT'::asset_attribute_data_type/);
|
||||
assert.match(migration, /false, true/);
|
||||
});
|
||||
|
||||
test('F6.3 Android follows Inspección → Acta → Hallazgo → Inventario', () => {
|
||||
const root = source('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/F3VisitRoot.kt');
|
||||
const acts = source('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/ui/MobileActsScreen.kt');
|
||||
const vm = source('../android-app/app/src/main/java/com/korexlabs/dhinspeccion/MainViewModel.kt');
|
||||
assert.match(root, /model\.startVisit\(\); onActs\(\)/);
|
||||
assert.match(root, /Text\("Nuevo Hallazgo"/);
|
||||
assert.match(root, /Elegí una Instalación o Subinstalación/);
|
||||
assert.match(acts, /Text\("\+ Agregar Hallazgo"\)/);
|
||||
assert.match(acts, /model\.createAct\(newActUrgency\)/);
|
||||
assert.match(vm, /fun createAct\(urgency: String = "NON_URGENT"\)/);
|
||||
assert.match(vm, /repository\.fieldInventory\(currentVisit\.id, search, parentId\)/);
|
||||
});
|
||||
""")
|
||||
|
||||
# Remove this one-shot workflow from the resulting source tree.
|
||||
Path('.github/workflows/_oneshot-mobile-act-finding-flow.yml').unlink()
|
||||
PY
|
||||
|
||||
- name: Commit patch
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "enlineawork"
|
||||
git config user.email "enlinea.work@gmail.com"
|
||||
git add -A
|
||||
git diff --cached --check
|
||||
git commit -m "fix(mobile): center field flow on Acta and Hallazgo"
|
||||
git push origin HEAD:fix/mobile-act-finding-flow
|
||||
Reference in New Issue
Block a user