picklist corrigido
This commit is contained in:
+22
-7
@@ -16,7 +16,12 @@ import {
|
||||
createClarizenClient,
|
||||
} from "./clarizen/client.js";
|
||||
import { formatFieldHint } from "./metadata/field-hints.js";
|
||||
import { isPickListField } from "./metadata/picklist.js";
|
||||
import { getEntityMetadata, pickEntityType, pickField } from "./metadata/picker.js";
|
||||
import {
|
||||
promptPickListValue,
|
||||
type FieldValueSelection,
|
||||
} from "./parse/prompt-picklist.js";
|
||||
import {
|
||||
formatValueForDisplay,
|
||||
parseFieldValue,
|
||||
@@ -27,7 +32,7 @@ import { fetchAllEntities } from "./query/fetch-all.js";
|
||||
import { promptEntityFilter } from "./query/prompt-filter.js";
|
||||
import { buildRunLog, writeRunLog } from "./log/run-log.js";
|
||||
import { bulkUpdateField } from "./update/bulk-field.js";
|
||||
import type { FieldDescription, JsonValue } from "@arco/clarizen-lib";
|
||||
import type { Clarizen, FieldDescription, JsonValue } from "@arco/clarizen-lib";
|
||||
|
||||
function exitCancel(): never {
|
||||
cancel("Operação cancelada.");
|
||||
@@ -35,8 +40,13 @@ function exitCancel(): never {
|
||||
}
|
||||
|
||||
async function promptFieldValue(
|
||||
clarizen: Clarizen,
|
||||
field: FieldDescription,
|
||||
): Promise<JsonValue | symbol> {
|
||||
): Promise<FieldValueSelection | symbol> {
|
||||
if (isPickListField(field)) {
|
||||
return promptPickListValue(clarizen, field);
|
||||
}
|
||||
|
||||
console.log("\n" + formatFieldHint(field) + "\n");
|
||||
|
||||
if (field.nullable) {
|
||||
@@ -48,7 +58,7 @@ async function promptFieldValue(
|
||||
],
|
||||
});
|
||||
if (isCancel(action)) return action;
|
||||
if (action === "clear") return null;
|
||||
if (action === "clear") return { value: null, displayLabel: "(vazio / null)" };
|
||||
}
|
||||
|
||||
while (true) {
|
||||
@@ -64,7 +74,8 @@ async function promptFieldValue(
|
||||
if (isCancel(raw)) return raw;
|
||||
|
||||
try {
|
||||
return parseFieldValue(raw, field);
|
||||
const value = parseFieldValue(raw, field);
|
||||
return { value };
|
||||
} catch (err) {
|
||||
if (err instanceof ValueParseError) {
|
||||
console.error(`\n Erro: ${err.message}\n`);
|
||||
@@ -107,8 +118,11 @@ async function main(): Promise<void> {
|
||||
const { field } = fieldChoice;
|
||||
const fieldName = field.name!;
|
||||
|
||||
const parsedValue = await promptFieldValue(field);
|
||||
if (isCancel(parsedValue)) exitCancel();
|
||||
const valueSelection = await promptFieldValue(clarizen, field);
|
||||
if (isCancel(valueSelection)) exitCancel();
|
||||
|
||||
const parsedValue = valueSelection.value;
|
||||
const valueDisplayLabel = valueSelection.displayLabel;
|
||||
|
||||
const fetchSpinner = spinner();
|
||||
fetchSpinner.start("Buscando objetos...");
|
||||
@@ -137,7 +151,7 @@ Resumo da operação:
|
||||
Tipo: ${entityLabel}
|
||||
Filtro: ${formatFilterSummary(entityFilter)}
|
||||
Campo: ${field.label ?? fieldName} (${fieldName}) — ${field.type ?? "?"}
|
||||
Valor: ${formatValueForDisplay(parsedValue)}
|
||||
Valor: ${formatValueForDisplay(parsedValue, valueDisplayLabel)}
|
||||
Objetos: ${records.length.toLocaleString("pt-BR")} registro(s)
|
||||
`);
|
||||
|
||||
@@ -168,6 +182,7 @@ Resumo da operação:
|
||||
field,
|
||||
fieldName,
|
||||
parsedValue,
|
||||
valueDisplayLabel,
|
||||
records,
|
||||
result,
|
||||
}),
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface RunLog {
|
||||
fieldLabel?: string;
|
||||
fieldType?: string;
|
||||
value: JsonValue;
|
||||
valueLabel?: string;
|
||||
};
|
||||
summary: {
|
||||
total: number;
|
||||
@@ -43,6 +44,7 @@ export interface BuildRunLogParams {
|
||||
field: FieldDescription;
|
||||
fieldName: string;
|
||||
parsedValue: JsonValue;
|
||||
valueDisplayLabel?: string;
|
||||
records: EntityRecord[];
|
||||
result: BulkUpdateResult;
|
||||
}
|
||||
@@ -60,6 +62,7 @@ export function buildRunLog(params: BuildRunLogParams): RunLog {
|
||||
field,
|
||||
fieldName,
|
||||
parsedValue,
|
||||
valueDisplayLabel,
|
||||
records,
|
||||
result,
|
||||
} = params;
|
||||
@@ -101,6 +104,7 @@ export function buildRunLog(params: BuildRunLogParams): RunLog {
|
||||
fieldLabel: field.label,
|
||||
fieldType: field.type,
|
||||
value: parsedValue,
|
||||
valueLabel: valueDisplayLabel,
|
||||
},
|
||||
summary: {
|
||||
total: records.length,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { FieldDescription, FieldType } from "@arco/clarizen-lib";
|
||||
import { isPickListField } from "./picklist.js";
|
||||
|
||||
const TYPE_EXAMPLES: Record<FieldType, string> = {
|
||||
Boolean: "true, false, sim, não",
|
||||
@@ -20,11 +21,20 @@ export function getFieldTypeExample(type: FieldType | undefined): string {
|
||||
|
||||
export function formatFieldHint(field: FieldDescription): string {
|
||||
const type = field.type ?? "desconhecido";
|
||||
const example = getFieldTypeExample(field.type);
|
||||
const lines = [
|
||||
`Tipo: ${type}`,
|
||||
`Exemplo: ${example}`,
|
||||
];
|
||||
const lines: string[] = [];
|
||||
|
||||
if (isPickListField(field)) {
|
||||
lines.push("Tipo: PickList (lista de opções)");
|
||||
lines.push("Escolha um valor na lista — não digite o nome exibido manualmente.");
|
||||
const refType = (field as { referencedEntities?: string[] }).referencedEntities?.[0];
|
||||
if (refType) {
|
||||
lines.push(`Opções carregadas de: ${refType}`);
|
||||
}
|
||||
} else {
|
||||
const example = getFieldTypeExample(field.type);
|
||||
lines.push(`Tipo: ${type}`);
|
||||
lines.push(`Exemplo: ${example}`);
|
||||
}
|
||||
|
||||
if (field.nullable) {
|
||||
lines.push("Este campo aceita valor vazio (null) para limpar.");
|
||||
@@ -39,8 +49,9 @@ export function formatFieldHint(field: FieldDescription): string {
|
||||
export function formatFieldOptionLabel(field: FieldDescription): string {
|
||||
const label = field.label?.trim() || field.name || "?";
|
||||
const name = field.name ?? "?";
|
||||
const type = field.type ?? "?";
|
||||
return `${label} (${name}) — ${type}`;
|
||||
const presentation =
|
||||
field.presentationType === "PickList" ? "PickList" : (field.type ?? "?");
|
||||
return `${label} (${name}) — ${presentation}`;
|
||||
}
|
||||
|
||||
export function isUpdatableField(field: FieldDescription): boolean {
|
||||
|
||||
@@ -63,7 +63,7 @@ async function pickFromList<T extends { value: string; label: string }>(
|
||||
return select({ message, options });
|
||||
}
|
||||
|
||||
async function pickFromFiltered<T extends { value: string; label: string }>(
|
||||
export async function pickFromFiltered<T extends { value: string; label: string }>(
|
||||
message: string,
|
||||
allOptions: T[],
|
||||
filterPrompt: string,
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { Clarizen, FieldDescription } from "@arco/clarizen-lib";
|
||||
import { resolveDisplayField } from "../query/resolve-display-field.js";
|
||||
|
||||
const PAGE_SIZE = 500;
|
||||
|
||||
export interface FieldWithRefs extends FieldDescription {
|
||||
referencedEntities?: string[];
|
||||
}
|
||||
|
||||
export interface PickListOption {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export function isPickListField(field: FieldWithRefs): boolean {
|
||||
return field.presentationType === "PickList";
|
||||
}
|
||||
|
||||
export function getPickListEntityType(field: FieldWithRefs): string | undefined {
|
||||
return field.referencedEntities?.[0];
|
||||
}
|
||||
|
||||
function extractLabel(
|
||||
entity: Record<string, unknown>,
|
||||
displayField: string,
|
||||
): string | undefined {
|
||||
const objectFields = entity.objectFields as Record<string, unknown> | undefined;
|
||||
const value = entity[displayField] ?? objectFields?.[displayField];
|
||||
if (value === null || value === undefined) return undefined;
|
||||
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
||||
return String(value);
|
||||
}
|
||||
if (typeof value === "object" && value !== null && "name" in value) {
|
||||
const name = (value as { name?: unknown }).name;
|
||||
if (name !== null && name !== undefined) return String(name);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function idSuffix(id: string): string {
|
||||
return id.includes("/") ? id.split("/").pop()! : id;
|
||||
}
|
||||
|
||||
export async function fetchPickListOptions(
|
||||
clarizen: Clarizen,
|
||||
pickListTypeName: string,
|
||||
onProgress?: (count: number) => void,
|
||||
): Promise<PickListOption[]> {
|
||||
const meta = await clarizen.metadata.describeMetadata({
|
||||
typeNames: [pickListTypeName],
|
||||
flags: ["fields"],
|
||||
});
|
||||
const entityMeta = meta.entityDescriptions?.find(
|
||||
(e) => e.typeName === pickListTypeName,
|
||||
);
|
||||
if (!entityMeta) {
|
||||
throw new Error(`Metadata não encontrada para picklist ${pickListTypeName}.`);
|
||||
}
|
||||
|
||||
const displayField = resolveDisplayField(entityMeta);
|
||||
const options: PickListOption[] = [];
|
||||
let from = 0;
|
||||
|
||||
while (true) {
|
||||
const result = await clarizen.data.entityQuery({
|
||||
typeName: pickListTypeName,
|
||||
fields: [displayField],
|
||||
paging: { from, limit: PAGE_SIZE },
|
||||
});
|
||||
|
||||
for (const entity of result.entities) {
|
||||
if (!entity.id) continue;
|
||||
const id = String(entity.id);
|
||||
const label =
|
||||
extractLabel(entity as Record<string, unknown>, displayField) ??
|
||||
idSuffix(id);
|
||||
options.push({ id, label });
|
||||
}
|
||||
|
||||
onProgress?.(options.length);
|
||||
|
||||
if (!result.paging?.hasMore) break;
|
||||
from += PAGE_SIZE;
|
||||
}
|
||||
|
||||
return options.sort((a, b) => a.label.localeCompare(b.label, "pt-BR"));
|
||||
}
|
||||
|
||||
export function formatPickListOptionLabel(option: PickListOption): string {
|
||||
return `${option.label} (${option.id})`;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { Clarizen, JsonValue } from "@arco/clarizen-lib";
|
||||
import { isCancel, select, spinner, text } from "@clack/prompts";
|
||||
import { formatFieldHint } from "../metadata/field-hints.js";
|
||||
import {
|
||||
fetchPickListOptions,
|
||||
formatPickListOptionLabel,
|
||||
getPickListEntityType,
|
||||
type FieldWithRefs,
|
||||
} from "../metadata/picklist.js";
|
||||
import { pickFromFiltered } from "../metadata/picker.js";
|
||||
import { parseFieldValue, ValueParseError } from "./value.js";
|
||||
|
||||
export interface FieldValueSelection {
|
||||
value: JsonValue;
|
||||
displayLabel?: string;
|
||||
}
|
||||
|
||||
async function pickPickListOption(
|
||||
field: FieldWithRefs,
|
||||
options: Array<{ value: string; label: string }>,
|
||||
): Promise<string | symbol> {
|
||||
const fieldLabel = field.label ?? field.name ?? "valor";
|
||||
return pickFromFiltered(
|
||||
`Escolha o valor para ${fieldLabel}`,
|
||||
options,
|
||||
"Como deseja buscar na lista?",
|
||||
);
|
||||
}
|
||||
|
||||
async function promptPickListSelection(
|
||||
clarizen: Clarizen,
|
||||
field: FieldWithRefs,
|
||||
pickListTypeName: string,
|
||||
): Promise<FieldValueSelection | symbol> {
|
||||
const loadSpinner = spinner();
|
||||
loadSpinner.start(`Carregando opções de ${pickListTypeName}...`);
|
||||
|
||||
const pickListOptions = await fetchPickListOptions(
|
||||
clarizen,
|
||||
pickListTypeName,
|
||||
(count) => {
|
||||
loadSpinner.message(
|
||||
`Carregando opções de ${pickListTypeName}... (${count})`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
loadSpinner.stop(`${pickListOptions.length} opção(ões) carregada(s).`);
|
||||
|
||||
if (pickListOptions.length === 0) {
|
||||
console.log(
|
||||
`\nNenhuma opção encontrada para ${pickListTypeName}. Informe o ID manualmente.\n`,
|
||||
);
|
||||
return promptManualEntityValue(field);
|
||||
}
|
||||
|
||||
const selectOptions = pickListOptions.map((o) => ({
|
||||
value: o.id,
|
||||
label: formatPickListOptionLabel(o),
|
||||
}));
|
||||
|
||||
const selectedId = await pickPickListOption(field, selectOptions);
|
||||
if (isCancel(selectedId)) return selectedId;
|
||||
|
||||
const match = pickListOptions.find((o) => o.id === selectedId);
|
||||
return {
|
||||
value: { id: selectedId },
|
||||
displayLabel: match
|
||||
? `${match.label} (${match.id})`
|
||||
: String(selectedId),
|
||||
};
|
||||
}
|
||||
|
||||
async function promptManualEntityValue(
|
||||
field: FieldWithRefs,
|
||||
): Promise<FieldValueSelection | symbol> {
|
||||
while (true) {
|
||||
const raw = await text({
|
||||
message: "Informe o ID da entidade (ex: /Tipo/guid)",
|
||||
validate: (value) => (!value?.trim() ? "Valor obrigatório." : undefined),
|
||||
});
|
||||
if (isCancel(raw)) return raw;
|
||||
|
||||
try {
|
||||
const value = parseFieldValue(raw, field);
|
||||
return { value, displayLabel: formatEntityDisplayLabel(value) };
|
||||
} catch (err) {
|
||||
if (err instanceof ValueParseError) {
|
||||
console.error(`\n Erro: ${err.message}\n`);
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatEntityDisplayLabel(value: JsonValue): string | undefined {
|
||||
if (value && typeof value === "object" && "id" in value) {
|
||||
const id = (value as { id?: unknown }).id;
|
||||
if (typeof id === "string") return id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function promptPickListValue(
|
||||
clarizen: Clarizen,
|
||||
field: FieldWithRefs,
|
||||
): Promise<FieldValueSelection | symbol> {
|
||||
console.log("\n" + formatFieldHint(field) + "\n");
|
||||
|
||||
if (field.nullable) {
|
||||
const action = await select({
|
||||
message: "O que deseja fazer com este campo?",
|
||||
options: [
|
||||
{ value: "set", label: "Definir um valor" },
|
||||
{ value: "clear", label: "Limpar campo (null)" },
|
||||
],
|
||||
});
|
||||
if (isCancel(action)) return action;
|
||||
if (action === "clear") {
|
||||
return { value: null, displayLabel: "(vazio / null)" };
|
||||
}
|
||||
}
|
||||
|
||||
const pickListTypeName = getPickListEntityType(field);
|
||||
if (!pickListTypeName) {
|
||||
console.log(
|
||||
"\nPicklist sem tipo de referência na metadata. Informe o ID manualmente.\n",
|
||||
);
|
||||
return promptManualEntityValue(field);
|
||||
}
|
||||
|
||||
return promptPickListSelection(clarizen, field, pickListTypeName);
|
||||
}
|
||||
+9
-1
@@ -111,8 +111,16 @@ export function parseFieldValue(
|
||||
return parseByType(raw, field.type);
|
||||
}
|
||||
|
||||
export function formatValueForDisplay(value: JsonValue): string {
|
||||
export function formatValueForDisplay(
|
||||
value: JsonValue,
|
||||
displayLabel?: string,
|
||||
): string {
|
||||
if (displayLabel) return displayLabel;
|
||||
if (value === null) return "(vazio / null)";
|
||||
if (typeof value === "string") return `"${value}"`;
|
||||
if (typeof value === "object" && value !== null && "id" in value) {
|
||||
const id = (value as { id?: unknown }).id;
|
||||
if (typeof id === "string") return id;
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user