2026-10-16 10:24:39 -05:00
|
|
|
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
|
2026-10-20 15:46:00 -05:00
|
|
|
import { CheckboxField, DateField, LookupField, NumberField, SelectField, TextField, TextareaField, UdfField } from '../../shared/form';
|
2026-10-16 10:24:39 -05:00
|
|
|
import { WAREHOUSE_LOOKUP } from '../documents/document-layouts';
|
2026-10-19 19:01:44 -05:00
|
|
|
import type { VhLookup } from '../../shared/value-help/value-help.types';
|
2026-10-16 10:24:39 -05:00
|
|
|
import type { HeaderField } from '../documents/document-layouts';
|
|
|
|
|
|
|
|
|
|
const text = (v: unknown): string => (v === null || v === undefined ? '' : String(v));
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Dibuja un campo de un formulario de maestro según su tipo. Recibe el valor tal como está en SAP
|
|
|
|
|
* (las casillas son `tYES` / `tNO`) y emite el nuevo valor en esa misma forma.
|
|
|
|
|
*/
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'sbo-master-field',
|
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
2026-10-20 15:46:00 -05:00
|
|
|
imports: [CheckboxField, DateField, LookupField, NumberField, SelectField, TextField, TextareaField, UdfField],
|
2026-10-16 10:24:39 -05:00
|
|
|
template: `
|
|
|
|
|
@switch (field().type) {
|
|
|
|
|
@case ('checkbox') {
|
|
|
|
|
<sbo-checkbox-field
|
2026-10-20 08:59:58 -05:00
|
|
|
[label]="field().label" [dbName]="dbName()"
|
2026-10-16 10:24:39 -05:00
|
|
|
[readonly]="readOnly() || !!ro()"
|
|
|
|
|
[value]="value() === 'tYES'"
|
|
|
|
|
(valueChange)="changed.emit($event ? 'tYES' : 'tNO')"
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
@case ('lookup') {
|
|
|
|
|
<sbo-lookup-field
|
2026-10-20 08:59:58 -05:00
|
|
|
[label]="field().label" [dbName]="dbName()"
|
2026-10-16 10:24:39 -05:00
|
|
|
[required]="required()"
|
|
|
|
|
[readonly]="readOnly()"
|
|
|
|
|
[value]="str()"
|
|
|
|
|
(valueChange)="changed.emit(typed($event))"
|
|
|
|
|
[lookup]="lookup()"
|
|
|
|
|
resolveOnLeave
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
@case ('warehouse') {
|
|
|
|
|
<sbo-lookup-field
|
2026-10-20 08:59:58 -05:00
|
|
|
[label]="field().label" [dbName]="dbName()"
|
2026-10-16 10:24:39 -05:00
|
|
|
[required]="required()"
|
|
|
|
|
[readonly]="readOnly()"
|
|
|
|
|
[value]="str()"
|
|
|
|
|
(valueChange)="changed.emit($event)"
|
|
|
|
|
[lookup]="warehouse"
|
|
|
|
|
resolveOnLeave
|
|
|
|
|
/>
|
|
|
|
|
}
|
2026-10-20 15:46:00 -05:00
|
|
|
@case ('udf') {
|
|
|
|
|
<sbo-udf-field
|
|
|
|
|
[label]="field().label" [dbName]="dbName()"
|
|
|
|
|
[table]="udf().table" [alias]="udf().alias"
|
|
|
|
|
[required]="required()"
|
|
|
|
|
[readonly]="readOnly() || !!ro()"
|
|
|
|
|
[value]="value()"
|
|
|
|
|
(valueChange)="changed.emit($any($event))"
|
|
|
|
|
/>
|
|
|
|
|
}
|
2026-10-16 10:24:39 -05:00
|
|
|
@case ('select') {
|
|
|
|
|
<sbo-select-field
|
2026-10-20 08:59:58 -05:00
|
|
|
[label]="field().label" [dbName]="dbName()"
|
2026-10-16 10:24:39 -05:00
|
|
|
[required]="required()"
|
|
|
|
|
[readonly]="readOnly()"
|
|
|
|
|
[options]="options()"
|
|
|
|
|
placeholder=""
|
|
|
|
|
[value]="str() || null"
|
|
|
|
|
(valueChange)="changed.emit($event ?? '')"
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
@case ('number') {
|
|
|
|
|
<sbo-number-field
|
2026-10-20 08:59:58 -05:00
|
|
|
[label]="field().label" [dbName]="dbName()"
|
2026-10-16 10:24:39 -05:00
|
|
|
[decimals]="decimals()"
|
|
|
|
|
[readonly]="readOnly() || !!ro()"
|
|
|
|
|
[value]="num()"
|
|
|
|
|
(valueChange)="changed.emit($event ?? 0)"
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
@case ('date') {
|
2026-10-20 08:59:58 -05:00
|
|
|
<sbo-date-field [label]="field().label" [dbName]="dbName()" [required]="required()" [readonly]="readOnly()" [value]="str()" (valueChange)="changed.emit($event)" />
|
2026-10-16 10:24:39 -05:00
|
|
|
}
|
|
|
|
|
@case ('textarea') {
|
2026-10-20 08:59:58 -05:00
|
|
|
<sbo-textarea-field [label]="field().label" [dbName]="dbName()" [readonly]="readOnly()" [value]="str()" (valueChange)="changed.emit($event)" />
|
2026-10-16 10:24:39 -05:00
|
|
|
}
|
|
|
|
|
@default {
|
|
|
|
|
<sbo-text-field
|
2026-10-20 08:59:58 -05:00
|
|
|
[label]="field().label" [dbName]="dbName()"
|
2026-10-16 10:24:39 -05:00
|
|
|
[required]="required()"
|
|
|
|
|
[readonly]="readOnly() || !!ro()"
|
|
|
|
|
[value]="str()"
|
|
|
|
|
(valueChange)="changed.emit($event)"
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
styles: ':host { display: block; }',
|
|
|
|
|
})
|
|
|
|
|
export class MasterField {
|
|
|
|
|
readonly field = input.required<HeaderField>();
|
|
|
|
|
readonly value = input<unknown>('');
|
|
|
|
|
/** Todo el formulario en solo lectura. */
|
|
|
|
|
readonly readOnly = input(false);
|
|
|
|
|
/** El registro ya existe: los campos `immutable` (el código) no se pueden cambiar. */
|
|
|
|
|
readonly saved = input(false);
|
|
|
|
|
readonly changed = output<string | number>();
|
|
|
|
|
|
2026-10-20 15:46:00 -05:00
|
|
|
protected readonly udf = computed(() => {
|
|
|
|
|
const f = this.field();
|
|
|
|
|
if (f.type !== 'udf') throw new Error('campo sin definición de usuario');
|
|
|
|
|
return f;
|
|
|
|
|
});
|
|
|
|
|
|
2026-10-19 19:01:44 -05:00
|
|
|
protected readonly warehouse: VhLookup = { ...WAREHOUSE_LOOKUP, nameKey: 'WarehouseName' };
|
2026-10-16 10:24:39 -05:00
|
|
|
|
|
|
|
|
/** Un código numérico de SAP (grupo, lista de precios) se guarda como número. */
|
|
|
|
|
protected typed(code: string): string | number {
|
|
|
|
|
const f = this.field();
|
|
|
|
|
return f.type === 'lookup' && f.lookup.valueType === 'number' && /^\d+$/.test(code.trim()) ? Number(code) : code;
|
|
|
|
|
}
|
2026-10-20 08:19:55 -05:00
|
|
|
/** El texto del campo; una fecha de SAP llega como `2031-10-31T00:00:00Z` y el selector de fecha pide `yyyy-MM-dd`. */
|
2026-10-20 08:59:58 -05:00
|
|
|
protected readonly dbName = computed(() => {
|
|
|
|
|
const f = this.field();
|
|
|
|
|
return 'key' in f ? f.key : undefined;
|
|
|
|
|
});
|
2026-10-20 08:19:55 -05:00
|
|
|
protected readonly str = computed(() => (this.field().type === 'date' ? text(this.value()).slice(0, 10) : text(this.value())));
|
2026-10-16 10:24:39 -05:00
|
|
|
protected readonly num = computed(() => (this.value() === '' || this.value() === null || this.value() === undefined ? null : Number(this.value())));
|
|
|
|
|
protected readonly required = computed(() => {
|
|
|
|
|
const f = this.field();
|
|
|
|
|
return 'required' in f && f.required === true;
|
|
|
|
|
});
|
|
|
|
|
protected readonly ro = computed(() => {
|
|
|
|
|
const f = this.field();
|
2026-10-16 14:36:24 -05:00
|
|
|
if ('immutable' in f && f.immutable && this.saved()) return true;
|
|
|
|
|
return 'readonly' in f ? !!f.readonly : false;
|
2026-10-16 10:24:39 -05:00
|
|
|
});
|
|
|
|
|
protected readonly lookup = computed(() => {
|
|
|
|
|
const f = this.field();
|
|
|
|
|
if (f.type !== 'lookup') throw new Error('campo sin lista de valores');
|
|
|
|
|
return f.lookup;
|
|
|
|
|
});
|
|
|
|
|
protected readonly options = computed(() => {
|
|
|
|
|
const f = this.field();
|
|
|
|
|
return f.type === 'select' ? f.options : [];
|
|
|
|
|
});
|
|
|
|
|
protected readonly decimals = computed(() => {
|
|
|
|
|
const f = this.field();
|
|
|
|
|
return f.type === 'number' ? f.decimals : undefined;
|
|
|
|
|
});
|
|
|
|
|
}
|