@agridea/suibi-module 0.1.0-preview.3b0cded → 0.1.0-preview.3e5b741

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 (77) hide show
  1. package/dist/elements/browser/main.js +70 -70
  2. package/dist/elements/browser/styles.css +1 -1
  3. package/dist/elements/browser/suibi-module-latest.css +1 -1
  4. package/dist/elements/browser/suibi-module-latest.js +70 -70
  5. package/dist/elements/browser/{suibi-module.v0.1.0-preview.3b0cded.css → suibi-module.v0.1.0-preview.3e5b741.css} +1 -1
  6. package/dist/elements/browser/suibi-module.v0.1.0-preview.3e5b741.js +78 -0
  7. package/dist/elements/browser/version.txt +1 -1
  8. package/dist/elements/browser/wc-global-utilities.css +1 -1
  9. package/dist/types/app/components/data-preview/data-preview.component.d.ts +222 -0
  10. package/dist/types/app/components/inline-table/inline-table.component.d.ts +73 -6
  11. package/dist/types/app/components/inline-table/inline-table.types.d.ts +89 -4
  12. package/dist/types/app/components/suissebilanz-dashboard/suissebilanz-dashboard.component.d.ts +2 -0
  13. package/dist/types/app/core/i18n/i18n.service.d.ts +3 -0
  14. package/dist/types/app/core/navigation/navigation.service.d.ts +7 -0
  15. package/dist/types/app/core/planning/planning-balance.service.d.ts +8 -0
  16. package/dist/types/app/core/utils/custom-unit.util.d.ts +39 -0
  17. package/dist/types/app/core/utils/object.util.d.ts +22 -0
  18. package/dist/types/app/core/validation/ajv-custom.d.ts +1 -0
  19. package/dist/types/app/core/validation/capabilities.d.ts +6 -0
  20. package/dist/types/app/core/validation/global-validation.service.d.ts +2 -0
  21. package/dist/types/app/jsonforms/renderers/controls/selects/animal-category/animal-category-select.renderer.d.ts +4 -1
  22. package/dist/types/app/jsonforms/renderers/controls/selects/code-list/code-list-select.renderer.d.ts +4 -1
  23. package/dist/types/app/jsonforms/renderers/layout/group/module-toggle-group.renderer.d.ts +3 -1
  24. package/dist/types/app/jsonforms/shared/searchable-select/search-highlight.d.ts +18 -0
  25. package/dist/types/app/jsonforms/shared/searchable-select/searchable-select.component.d.ts +63 -0
  26. package/dist/types/app/nutrient-cycle/animal-stock/{animal-stock-detail.component.d.ts → animal-stock-detail/animal-stock-detail.component.d.ts} +3 -2
  27. package/dist/types/app/nutrient-cycle/animal-stock/{animal-stock-list.component.d.ts → animal-stock-list/animal-stock-list.component.d.ts} +1 -1
  28. package/dist/types/app/nutrient-cycle/animal-stock/{animal-stock-master-detail.component.d.ts → animal-stock-master-detail/animal-stock-master-detail.component.d.ts} +2 -2
  29. package/dist/types/app/nutrient-cycle/animal-stock/index.d.ts +3 -3
  30. package/dist/types/app/nutrient-cycle/farm-manure-trading/farm-manure-trading-master-detail/farm-manure-trading-master-detail.component.d.ts +19 -0
  31. package/dist/types/app/nutrient-cycle/farm-manure-trading/farm-manure-trading-table/farm-manure-trading-table.component.d.ts +37 -0
  32. package/dist/types/app/nutrient-cycle/farm-manure-trading/farm-manure-trading-table/farm-manure-trading-table.config.d.ts +24 -0
  33. package/dist/types/app/nutrient-cycle/farm-manure-trading/farm-manure-trading.types.d.ts +4 -0
  34. package/dist/types/app/nutrient-cycle/farm-manure-trading/index.d.ts +3 -3
  35. package/dist/types/app/nutrient-cycle/farm-manure-trading/transaction-unit.util.d.ts +2 -0
  36. package/dist/types/app/nutrient-cycle/field-cultivation/arable-land-table/arable-land-table.component.d.ts +23 -0
  37. package/dist/types/app/nutrient-cycle/field-cultivation/arable-land-table/arable-land-table.config.d.ts +16 -0
  38. package/dist/types/app/nutrient-cycle/field-cultivation/crop-harvest-table/crop-harvest-table.component.d.ts +21 -0
  39. package/dist/types/app/nutrient-cycle/field-cultivation/crop-harvest-table/crop-harvest-table.config.d.ts +13 -0
  40. package/dist/types/app/nutrient-cycle/field-cultivation/{field-cultivation-detail.component.d.ts → field-cultivation-detail/field-cultivation-detail.component.d.ts} +2 -2
  41. package/dist/types/app/nutrient-cycle/field-cultivation/{field-cultivation-general.component.d.ts → field-cultivation-general/field-cultivation-general.component.d.ts} +2 -4
  42. package/dist/types/app/nutrient-cycle/field-cultivation/{field-cultivation-list.component.d.ts → field-cultivation-list/field-cultivation-list.component.d.ts} +1 -1
  43. package/dist/types/app/nutrient-cycle/field-cultivation/field-cultivation-master-detail/field-cultivation-master-detail.component.d.ts +29 -0
  44. package/dist/types/app/nutrient-cycle/field-cultivation/field-cultivation.types.d.ts +6 -1
  45. package/dist/types/app/nutrient-cycle/field-cultivation/field-cultivation.util.d.ts +4 -0
  46. package/dist/types/app/nutrient-cycle/field-cultivation/fodder-cropland-table/fodder-cropland-table.component.d.ts +29 -0
  47. package/dist/types/app/nutrient-cycle/field-cultivation/fodder-cropland-table/fodder-cropland-table.config.d.ts +21 -0
  48. package/dist/types/app/nutrient-cycle/field-cultivation/index.d.ts +8 -3
  49. package/dist/types/app/nutrient-cycle/field-cultivation/special-crops-table/special-crops-table.component.d.ts +29 -0
  50. package/dist/types/app/nutrient-cycle/field-cultivation/special-crops-table/special-crops-table.config.d.ts +51 -0
  51. package/dist/types/app/nutrient-cycle/field-spreading/fertiliser-table/fertiliser-table.component.d.ts +26 -6
  52. package/dist/types/app/nutrient-cycle/field-spreading/field-spreading.component.d.ts +2 -0
  53. package/dist/types/app/nutrient-cycle/field-spreading/field-spreading.types.d.ts +2 -0
  54. package/dist/types/app/nutrient-cycle/field-spreading/ingredient.util.d.ts +8 -0
  55. package/dist/types/app/nutrient-cycle/field-spreading/recycling-table/recycling-table.component.d.ts +14 -4
  56. package/dist/types/app/nutrient-cycle/field-spreading/recycling-table/recycling-table.config.d.ts +4 -3
  57. package/dist/types/app/nutrient-cycle/field-spreading/recycling-table/recycling-transaction-unit.util.d.ts +4 -0
  58. package/dist/types/app/nutrient-cycle/fodder/fodder-master-detail/fodder-master-detail.component.d.ts +25 -0
  59. package/dist/types/app/nutrient-cycle/fodder/fodder-table/fodder-table.component.d.ts +28 -0
  60. package/dist/types/app/nutrient-cycle/fodder/fodder-table/fodder-table.config.d.ts +9 -0
  61. package/dist/types/app/nutrient-cycle/fodder/fodder.types.d.ts +7 -0
  62. package/dist/types/app/nutrient-cycle/fodder/index.d.ts +2 -3
  63. package/dist/types/app/nutrient-cycle/fodder/ingredient.util.d.ts +8 -0
  64. package/dist/types/app/nutrient-cycle/storage/index.d.ts +3 -3
  65. package/dist/types/app/nutrient-cycle/storage/{storage-detail.component.d.ts → storage-detail/storage-detail.component.d.ts} +3 -3
  66. package/dist/types/app/nutrient-cycle/storage/{storage-general.component.d.ts → storage-general/storage-general.component.d.ts} +4 -4
  67. package/dist/types/app/nutrient-cycle/storage/{storage-list.component.d.ts → storage-list/storage-list.component.d.ts} +4 -1
  68. package/dist/types/app/nutrient-cycle/storage/{storage-master-detail.component.d.ts → storage-master-detail/storage-master-detail.component.d.ts} +5 -2
  69. package/package.json +1 -1
  70. package/dist/elements/browser/suibi-module.v0.1.0-preview.3b0cded.js +0 -78
  71. package/dist/types/app/nutrient-cycle/farm-manure-trading/farm-manure-trading-detail.component.d.ts +0 -26
  72. package/dist/types/app/nutrient-cycle/farm-manure-trading/farm-manure-trading-list.component.d.ts +0 -22
  73. package/dist/types/app/nutrient-cycle/farm-manure-trading/farm-manure-trading-master-detail.component.d.ts +0 -25
  74. package/dist/types/app/nutrient-cycle/field-cultivation/field-cultivation-master-detail.component.d.ts +0 -37
  75. package/dist/types/app/nutrient-cycle/fodder/fodder-detail.component.d.ts +0 -31
  76. package/dist/types/app/nutrient-cycle/fodder/fodder-list.component.d.ts +0 -20
  77. package/dist/types/app/nutrient-cycle/fodder/fodder-master-detail.component.d.ts +0 -37
@@ -1 +1 @@
1
- 0.1.0-preview.3b0cded 3b0cded
1
+ 0.1.0-preview.3e5b741 3e5b741
@@ -1 +1 @@
1
- .cdk-overlay-container{position:fixed;inset:0;z-index:1200;pointer-events:none}.cdk-overlay-container:empty{display:none}.cdk-global-overlay-wrapper{position:fixed;inset:0;display:flex;align-items:flex-start;justify-content:center;pointer-events:none}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1200;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1200}.cdk-overlay-backdrop{position:fixed;inset:0;z-index:1199;pointer-events:auto}.cdk-overlay-transparent-backdrop{background:transparent}.cdk-overlay-dark-backdrop{background:#00000052}.mat-mdc-select-panel.mdc-menu-surface{background:#fff;--mdc-theme-surface: #fff;--mdc-menu-container-color: #fff;--mat-select-panel-background-color: #fff;box-shadow:0 4px 16px #00000040;border-radius:4px;outline:0;opacity:1!important;filter:none!important}
1
+ .cdk-overlay-container{position:fixed;inset:0;z-index:1200;pointer-events:none}.cdk-overlay-container:empty{display:none}.cdk-global-overlay-wrapper{position:fixed;inset:0;display:flex;align-items:flex-start;justify-content:center;pointer-events:none}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1200;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1200}.cdk-overlay-backdrop{position:fixed;inset:0;z-index:1199;pointer-events:auto}.cdk-overlay-transparent-backdrop{background:transparent}.cdk-overlay-dark-backdrop{background:#00000052}.mat-mdc-select-panel.mdc-menu-surface{background:#fff;--mdc-theme-surface: #fff;--mdc-menu-container-color: #fff;--mat-select-panel-background-color: #fff;box-shadow:0 4px 16px #00000040;border-radius:4px;outline:0;opacity:1!important;filter:none!important}.mat-mdc-option.mdc-list-item{height:auto;min-height:48px}.mat-mdc-option .mdc-list-item__primary-text{white-space:normal;overflow:visible;text-overflow:clip;line-height:1.35}
@@ -3,9 +3,231 @@ import { PlanningBalanceService } from '../../core/planning/planning-balance.ser
3
3
  export declare class DataPreviewComponent {
4
4
  private readonly host;
5
5
  private readonly emitter;
6
+ private readonly clipboard;
6
7
  readonly planning: PlanningBalanceService;
7
8
  readonly naebi: NaebiDataService;
8
9
  readonly metadataPreview: import("@angular/core").Signal<import("../../../public-api").SuibiDataEventMetadata>;
10
+ /** Exact payload that runPlanning will POST (same builder as the real request). */
11
+ readonly apiPayload: import("@angular/core").Signal<{
12
+ nutrientCycle: {
13
+ animalStock: {
14
+ order: string;
15
+ animalCategoryPRIF: number;
16
+ selfReportedLivestock: {
17
+ averageQuantity: number;
18
+ };
19
+ durationCorrectionQuantity: number;
20
+ durationCorrectionDays: number;
21
+ utilisation: number;
22
+ formOfDetention: {
23
+ hasGrazing: boolean;
24
+ pasture: {
25
+ days: number;
26
+ hoursPerDay: number;
27
+ };
28
+ stall: {
29
+ stableSystem: string;
30
+ stableVentilationType: number;
31
+ hasStableDeepLitter: boolean;
32
+ hasStableManureBeltDrying: boolean;
33
+ stableManureRemovalFrequency: number;
34
+ stableWaterSupply: number;
35
+ hasStableManureAndSlurryProduction: boolean;
36
+ hasStableFeedingStationElevated: boolean;
37
+ hasStableFloorSlopedUrineDrainage: boolean;
38
+ hasYard: boolean;
39
+ };
40
+ };
41
+ hasImpexDerivedExcretion: boolean;
42
+ hasLinearCorrectionDerivedExcretion: boolean;
43
+ feeding: {
44
+ fodderConsumption: number;
45
+ concentratedFeedstuffConsumption: number;
46
+ concentratedFeedStuffConsumption: number;
47
+ winterFeeding: {
48
+ feedingRoutine: {
49
+ typeOfFeedstuffProduct: string;
50
+ transactionUnit: string;
51
+ quantity: number;
52
+ }[];
53
+ dailyConcentratedFeedstuff: {
54
+ quantity: number;
55
+ };
56
+ };
57
+ summerFeeding: {
58
+ feedingRoutine: {
59
+ typeOfFeedstuffProduct: string;
60
+ transactionUnit: string;
61
+ quantity: number;
62
+ }[];
63
+ dailyConcentratedFeedstuff: {
64
+ quantity: number;
65
+ };
66
+ };
67
+ };
68
+ }[];
69
+ storage: {
70
+ stableStrawLitter: number;
71
+ slurrySupplyProportionStored: number;
72
+ createFacilitySpecificManureProduct: boolean;
73
+ createFacilitySpecificSlurryProduct: boolean;
74
+ slurryTankQuantity: number;
75
+ dilutionRatio: number;
76
+ manurePit: never[];
77
+ slurryTank: {
78
+ descriptor: string;
79
+ volumeInM3: number;
80
+ depthInM: number;
81
+ cover: number;
82
+ mixingFrequency: number;
83
+ manureDerivation: string[];
84
+ }[];
85
+ };
86
+ farmManureTrading: {
87
+ supply: never[];
88
+ dispense: {
89
+ quantity: number;
90
+ typeOfManureRecyclingProduct: string;
91
+ transactionUnit: string;
92
+ manureDerivation: {
93
+ zoologicalAnimal: string;
94
+ percentageAllocation: number;
95
+ }[];
96
+ ingredient: {
97
+ quantity: number;
98
+ molecularFormula: string;
99
+ unit: string;
100
+ }[];
101
+ }[];
102
+ };
103
+ fieldSpreading: {
104
+ soilPh: number;
105
+ slurry: {
106
+ quantity: number;
107
+ dilutionRatio: number;
108
+ otherApplicationTechnologies: {
109
+ applicationTechnologyType: number;
110
+ proportionOfArea: number;
111
+ }[];
112
+ applicationDuringWarmDays: number;
113
+ proportionAppliedSummerMonths: number;
114
+ proportionAppliedOtherMonths: number;
115
+ proportionAppliedAfter6PM: number;
116
+ dragHoseAreaSize: number;
117
+ };
118
+ manure: {
119
+ applicationIncorporationTime: number;
120
+ proportionAppliedSummerMonths: number;
121
+ proportionAppliedOtherMonths: number;
122
+ };
123
+ fertiliser: {
124
+ descriptor: string;
125
+ fertiliserProductCategory: string;
126
+ quantity: number;
127
+ transactionUnit: string;
128
+ ingredient: {
129
+ molecularFormula: string;
130
+ quantity: number;
131
+ unit: string;
132
+ }[];
133
+ }[];
134
+ recycling: {
135
+ typeOfManureRecyclingProduct: string;
136
+ quantity: number;
137
+ transactionUnit: string;
138
+ ingredient: {
139
+ molecularFormula: string;
140
+ quantity: number;
141
+ unit: string;
142
+ }[];
143
+ }[];
144
+ };
145
+ lifetimePerformance: {
146
+ milkProductionReport: number;
147
+ };
148
+ fodder: {
149
+ trading: {
150
+ supply: {
151
+ typeOfFeedstuffProduct: string;
152
+ quantity: number;
153
+ transactionUnit: string;
154
+ ingredient: {
155
+ quantity: number;
156
+ molecularFormula: string;
157
+ unit: string;
158
+ }[];
159
+ }[];
160
+ dispense: {
161
+ typeOfFeedstuffProduct: string;
162
+ quantity: number;
163
+ transactionUnit: string;
164
+ ingredient: never[];
165
+ }[];
166
+ };
167
+ cropFromNonFodderCropland: {
168
+ typeOfFeedstuffProduct: string;
169
+ quantity: number;
170
+ transactionUnit: string;
171
+ ingredient: {
172
+ quantity: number;
173
+ molecularFormula: string;
174
+ unit: string;
175
+ }[];
176
+ }[];
177
+ proportionStorageLoss: number;
178
+ totalBalanceErrorMargin: number;
179
+ };
180
+ fieldCultivation: {
181
+ buildingZoneAreaSize: number;
182
+ outdoorVegetableMainCropAreaSize: number;
183
+ indoorVegetableMainCropAreaSize: number;
184
+ feedingOfCropHarvest: {
185
+ agronomicCropCategory: string;
186
+ effectiveYield: number;
187
+ }[];
188
+ dispenseOfCropHarvest: {
189
+ agronomicCropCategory: string;
190
+ effectiveYield: number;
191
+ }[];
192
+ fodderCropland: ({
193
+ agronomicCropCategory: string;
194
+ isMainCrop: boolean;
195
+ areaSize: number;
196
+ calculateYieldThroughFodderBalance: boolean;
197
+ yieldPerSquareMeasure?: undefined;
198
+ } | {
199
+ agronomicCropCategory: string;
200
+ isMainCrop: boolean;
201
+ areaSize: number;
202
+ yieldPerSquareMeasure: number;
203
+ calculateYieldThroughFodderBalance?: undefined;
204
+ })[];
205
+ arableLand: {
206
+ agronomicCropCategory: string;
207
+ isMainCrop: boolean;
208
+ areaSize: number;
209
+ yieldPerSquareMeasure: number;
210
+ hasLongTermYieldDivergence: boolean;
211
+ }[];
212
+ special: {
213
+ agronomicCropCategory: string;
214
+ userSpecificPlantName: string;
215
+ isMainCrop: boolean;
216
+ areaSize: number;
217
+ requirementCorrection: {
218
+ quantity: number;
219
+ molecularFormula: string;
220
+ unit: string;
221
+ }[];
222
+ }[];
223
+ };
224
+ generateReport: boolean;
225
+ };
226
+ calculationVersion: {
227
+ version: string;
228
+ };
229
+ }>;
9
230
  setPlanningEndpoint(value: string | null): void;
10
231
  emitData(): void;
232
+ copyJson(getValue: () => unknown): void;
11
233
  }
@@ -1,5 +1,7 @@
1
+ import { MatCheckboxChange } from '@angular/material/checkbox';
1
2
  import { BaseCodeListEntry } from '../../jsonforms/shared/option-sources/code-lists';
2
- import { InlineColumnDef, InlineTableConfig } from './inline-table.types';
3
+ import { SearchableSelectOption } from '../../jsonforms/shared/searchable-select/searchable-select.component';
4
+ import { InlineColumnDef, InlineTableConfig, InlineCellErrorResolver } from './inline-table.types';
3
5
  /**
4
6
  * InlineTableComponent — generic inline-editable table for array-of-objects data.
5
7
  *
@@ -26,6 +28,8 @@ import { InlineColumnDef, InlineTableConfig } from './inline-table.types';
26
28
  */
27
29
  export declare class InlineTableComponent {
28
30
  private readonly i18n;
31
+ private readonly host;
32
+ constructor();
29
33
  /** Table configuration — columns, labels, and the row factory. Acts as the "uischema". */
30
34
  readonly config: import("@angular/core").InputSignal<InlineTableConfig>;
31
35
  /** Current array of row objects. Each key maps to a column's `key` or sub-array path. */
@@ -38,17 +42,35 @@ export declare class InlineTableComponent {
38
42
  /**
39
43
  * Optional business-logic transform applied after every cell edit — before emission.
40
44
  *
41
- * Signature: `(changedKey, newValue, updatedRow) => transformedRow`
45
+ * Signature: `(changedKey, newValue, updatedRow, previousRow) => transformedRow`
42
46
  *
43
47
  * - `changedKey`: the `InlineColumnDef.key` of the edited column.
44
48
  * - `newValue`: the raw new value (string, number, or null).
45
49
  * - `updatedRow`: the row object already reflecting the edit.
50
+ * - `previousRow`: the row as it was before the edit — needed when the side-effect
51
+ * depends on the replaced value (e.g. converting a quantity on a unit switch).
46
52
  * - Return the row (possibly further mutated) that should be stored.
47
53
  *
48
54
  * Use this for cross-field side-effects such as pre-populating nutrient % columns
49
55
  * when a product is selected. For forms without such logic, omit this input.
50
56
  */
51
- readonly rowTransform: import("@angular/core").InputSignal<((changedKey: string, newValue: unknown, row: Record<string, unknown>) => Record<string, unknown>) | null>;
57
+ readonly rowTransform: import("@angular/core").InputSignal<((changedKey: string, newValue: unknown, row: Record<string, unknown>, previousRow?: Record<string, unknown>) => Record<string, unknown>) | null>;
58
+ /**
59
+ * Optional per-cell validation resolver — returns a translated error message
60
+ * shown under the cell (cell turns red via `mat-form-field-invalid`), or `null`
61
+ * when valid. Inline tables bypass JSONForms/AJV, so business rules that must
62
+ * be visible while typing (e.g. percentage caps) are surfaced through this hook.
63
+ */
64
+ readonly cellError: import("@angular/core").InputSignal<InlineCellErrorResolver | null>;
65
+ /**
66
+ * Index of the row to visually highlight (e.g. after a validation-error deep-link).
67
+ * Also triggers a scrollIntoView so the row is on screen.
68
+ */
69
+ readonly highlightRowIndex: import("@angular/core").InputSignal<number | null>;
70
+ /** Resolves the per-cell error message via the optional `cellError` input (null = valid). */
71
+ errorForCell(row: Record<string, unknown>, col: InlineColumnDef): string | null;
72
+ /** True when any column of the row reports an error — drives the row-level red tint. */
73
+ rowHasError(row: Record<string, unknown>): boolean;
52
74
  /** Extracts a px value from a grid-format width string (e.g. 'minmax(120px, 1fr)' → '120px'). */
53
75
  colMinWidth(width?: string): string;
54
76
  /** Total table width = sum of all column px values + 40px for the delete column. */
@@ -62,16 +84,25 @@ export declare class InlineTableComponent {
62
84
  readonly emptyHint: import("@angular/core").Signal<string>;
63
85
  /** Returns the option array for a select column from the shared CODE_LISTS map. */
64
86
  getOptions(col: InlineColumnDef): BaseCodeListEntry[];
87
+ /** Options for a select column, narrowed per row when the column defines `optionsFilter`. */
88
+ optionsForRow(col: InlineColumnDef, row: Record<string, unknown>): BaseCodeListEntry[];
89
+ /** Projects a select column's options into the searchable select's {value, label} shape. */
90
+ searchOptionsForRow(col: InlineColumnDef, row: Record<string, unknown>): SearchableSelectOption[];
91
+ /** Same projection for the subrows key selector (options are row-independent there). */
92
+ subrowSearchOptions(col: InlineColumnDef): SearchableSelectOption[];
65
93
  /**
66
94
  * Returns the localized display label for a code-list option.
95
+ * - `optionLabelKey`: translates the i18n key returned by the column's resolver — for
96
+ * literal option lists whose labels live in the locales rather than in master data.
67
97
  * - `optionDisplay: 'id'`: returns `String(entry.id)` (e.g. "kg", "t").
68
- * - default (`'label'`): uses `descriptor.designation_eng / designation_deu`;
98
+ * - default (`'label'`): uses the localized `descriptor.designation_<suffix>`;
69
99
  * falls back to `String(entry.id)` when no descriptor is present.
70
100
  */
71
101
  optionLabel(entry: BaseCodeListEntry, col: InlineColumnDef): string;
72
102
  /**
73
103
  * Reads the current cell value for a column from a row object.
74
104
  *
105
+ * - `valueResolver` columns: fully derived value (e.g. from master data).
75
106
  * - Simple field: returns `row[col.key]`.
76
107
  * - Sub-array field: navigates `row[arrayPath]`, finds the entry matching
77
108
  * `arrayKeyField === arrayKeyValue`, and returns `entry[arrayValueField]`.
@@ -84,14 +115,50 @@ export declare class InlineTableComponent {
84
115
  private setValue;
85
116
  /** Called when a `mat-select` selection changes. */
86
117
  onSelectChange(rowIdx: number, col: InlineColumnDef, value: unknown): void;
87
- /** Called on `(change)` of a number `<input>` — parses the string value and guards NaN. */
118
+ /** Called on `(input)` of a number `<input>` — parses the string value and guards NaN.
119
+ * Uses `input` (not `change`) so every keystroke commits immediately: relying on blur
120
+ * meant edits were lost whenever the table was destroyed before the field lost focus
121
+ * (e.g. navigating to another page), even though switching same-page tabs hid the bug
122
+ * since mat-tab-group keeps inactive tab content mounted. */
88
123
  onNumberChange(rowIdx: number, col: InlineColumnDef, event: Event): void;
89
- /** Called on `(change)` of a text `<input>`. */
124
+ /** Called on `(input)` of a text `<input>` (see onNumberChange for why not `(change)`). */
90
125
  onTextChange(rowIdx: number, col: InlineColumnDef, event: Event): void;
126
+ /** Called when a `mat-checkbox` is toggled. */
127
+ onCheckboxChange(rowIdx: number, col: InlineColumnDef, change: MatCheckboxChange): void;
128
+ /** Whether the cell widget is disabled for this row (per-row constraint hook). */
129
+ isCellDisabled(col: InlineColumnDef, row: Record<string, unknown>): boolean;
91
130
  /** Appends a blank row produced by `config.createRow()`. */
92
131
  addRow(): void;
93
132
  /** Removes the row at `idx` from the array. */
94
133
  removeRow(idx: number): void;
134
+ /** Ids of the expanded sub-rows panels, keyed `${rowIdx}:${col.key}` (UI-only state). */
135
+ private readonly expandedSubrows;
136
+ isSubrowsExpanded(rowIdx: number, col: InlineColumnDef): boolean;
137
+ toggleSubrows(rowIdx: number, col: InlineColumnDef): void;
138
+ /** Reads the nested key/value entries of a sub-rows column (never null). */
139
+ getSubEntries(row: Record<string, unknown>, col: InlineColumnDef): Record<string, unknown>[];
140
+ /** Options for the key select of a sub-rows column. */
141
+ getSubrowOptions(col: InlineColumnDef): BaseCodeListEntry[];
142
+ /** Localized label for a sub-entry key (descriptor designation, id as fallback). */
143
+ subrowKeyLabel(col: InlineColumnDef, keyValue: unknown): string;
144
+ /** Collapsed-cell summary, e.g. "Bovins 25 %, Porcs 75 %" ('–' when empty). */
145
+ subrowsSummary(row: Record<string, unknown>, col: InlineColumnDef): string;
146
+ /** Sum of the value fields of all sub-entries. */
147
+ subEntryTotal(row: Record<string, unknown>, col: InlineColumnDef): number;
148
+ /** Whether the sub-entry values sum to the configured expected total. */
149
+ subEntryTotalOk(row: Record<string, unknown>, col: InlineColumnDef): boolean;
150
+ addSubEntry(rowIdx: number, col: InlineColumnDef): void;
151
+ /** Moves focus to the key select of a freshly added sub-entry. setTimeout defers past
152
+ * the parent's rows round-trip so the new sub-row exists in the DOM. */
153
+ private focusSubEntry;
154
+ removeSubEntry(rowIdx: number, col: InlineColumnDef, subIdx: number): void;
155
+ onSubEntryKeyChange(rowIdx: number, col: InlineColumnDef, subIdx: number, value: unknown): void;
156
+ /** Commits on every keystroke (not on blur) — same rationale as `onNumberChange`.
157
+ * When `expectedTotal` is set, the value is clamped to the remaining share so the
158
+ * sum can never exceed it; smaller intermediate keystrokes still go through. */
159
+ onSubEntryValueChange(rowIdx: number, col: InlineColumnDef, subIdx: number, event: Event): void;
160
+ /** Writes the sub-entry array back to the row, runs rowTransform, emits rowsChange. */
161
+ private updateSubEntries;
95
162
  /**
96
163
  * Core change handler: sets the new value on the row, applies the optional
97
164
  * `rowTransform` for any cross-field side-effects, then emits the updated array.
@@ -11,8 +11,58 @@
11
11
  * To reuse the table for a different domain, create a new config file and a thin
12
12
  * wrapper component. Only the config changes; the rendering component stays the same.
13
13
  */
14
- /** Supported cell widget types. Extend with new literal types as needed. */
15
- export type InlineCellType = 'select' | 'number' | 'text';
14
+ import type { BaseCodeListEntry } from '../../jsonforms/shared/option-sources/code-lists';
15
+ /** Supported cell widget types. Extend with new literal types as needed.
16
+ * 'readonly' renders the resolved value as plain text (no input) — for columns
17
+ * that are fully derived from other fields and must not be user-editable.
18
+ * 'checkbox' renders a boolean mat-checkbox bound to a true/false row field.
19
+ * 'subrows' renders an expandable editor for a nested list of key/percentage
20
+ * pairs (e.g. manureDerivation[]) — see `InlineSubrowsConfig`. */
21
+ export type InlineCellType = 'select' | 'number' | 'text' | 'readonly' | 'checkbox' | 'subrows';
22
+ /**
23
+ * Per-cell validation resolver — returns a translated error message to display
24
+ * under the cell, or `null` when the cell is valid. Passed as the `cellError`
25
+ * input of InlineTableComponent for domain tables that bypass JSONForms/AJV
26
+ * (inline tables are not schema-validated, so business rules like percentage
27
+ * caps must be surfaced visually through this hook).
28
+ */
29
+ export type InlineCellErrorResolver = (row: Record<string, unknown>, col: InlineColumnDef) => string | null;
30
+ /**
31
+ * Configuration for a `type: 'subrows'` column — an expandable editor for a nested
32
+ * array of `{ key, value }` entries (e.g. manureDerivation: zoologicalAnimal +
33
+ * percentageAllocation). The collapsed cell shows a joined summary; expanding it
34
+ * opens a floating panel with one select + number input per entry, an add button,
35
+ * and a total indicator (green when the sum matches `expectedTotal`).
36
+ */
37
+ export interface InlineSubrowsConfig {
38
+ /** Path to the nested array on the row object (e.g. `'manureDerivation'`). */
39
+ arrayPath: string;
40
+ /** Field holding the code-list key in each sub-entry (e.g. `'zoologicalAnimal'`). */
41
+ keyField: string;
42
+ /** Key into the shared `CODE_LISTS` map for the key select options. */
43
+ keyCodeListKey: string;
44
+ /** i18n key for the key sub-column header inside the panel. */
45
+ keyLabelKey: string;
46
+ /** Field holding the numeric share in each sub-entry (e.g. `'percentageAllocation'`). */
47
+ valueField: string;
48
+ /** i18n key for the value sub-column header inside the panel. */
49
+ valueLabelKey: string;
50
+ /** i18n key for the "add sub-entry" button label. */
51
+ addLabelKey: string;
52
+ /** i18n key for the expand toggle label (collapsed state). */
53
+ expandLabelKey: string;
54
+ /** i18n key for the collapse toggle label (expanded state). */
55
+ collapseLabelKey: string;
56
+ /** i18n key for the total indicator label. */
57
+ totalLabelKey: string;
58
+ /** Expected sum of the value fields (e.g. 100) — the indicator turns green on exact
59
+ * match (no more, no less) and entered values are clamped so the sum never exceeds it. */
60
+ expectedTotal?: number;
61
+ /** Min for the value number input (default 0). */
62
+ valueMin?: number;
63
+ /** Max for the value number input (default: none). */
64
+ valueMax?: number;
65
+ }
16
66
  /**
17
67
  * Definition for a single column in an inline table.
18
68
  *
@@ -45,16 +95,34 @@ export interface InlineColumnDef {
45
95
  /**
46
96
  * For `type: 'select'` — key into the shared `CODE_LISTS` map.
47
97
  * The generic component resolves the option list and their localized labels
48
- * automatically via the `descriptor.designation_eng / designation_deu` convention.
98
+ * automatically via the localized `descriptor.designation_<suffix>` convention.
49
99
  */
50
100
  codeListKey?: string;
51
101
  /**
52
102
  * For `type: 'select'` — how to display an option's label in the dropdown.
53
- * - `'label'` (default): use `descriptor.designation_eng/deu` — best for named products.
103
+ * - `'label'` (default): use the localized `descriptor.designation_<suffix>` — best for named products.
54
104
  * - `'id'`: use `String(entry.id)` verbatim — best for codes that are already
55
105
  * human-readable (e.g. unit codes like "kg", "t", "l").
56
106
  */
57
107
  optionDisplay?: 'label' | 'id';
108
+ /**
109
+ * For `type: 'select'` — optional per-row narrowing of the code-list options,
110
+ * e.g. to restrict the product list based on another cell of the same row.
111
+ * Evaluated for every option of every row, so keep it cheap.
112
+ */
113
+ optionsFilter?: (entry: BaseCodeListEntry, row: Record<string, unknown>) => boolean;
114
+ /**
115
+ * For `type: 'select'` — resolve an option's label through an i18n key instead of the
116
+ * master-data descriptor (for literal option lists whose labels live in the locales,
117
+ * e.g. a row-type selector). Takes precedence over `optionDisplay`.
118
+ */
119
+ optionLabelKey?: (entry: BaseCodeListEntry) => string;
120
+ /**
121
+ * Optional i18n key for an explanatory tooltip shown via an info icon next to
122
+ * the column header (e.g. to explain a checkbox whose effect is not obvious).
123
+ * Omit for columns that need no extra explanation.
124
+ */
125
+ tooltipKey?: string;
58
126
  /**
59
127
  * CSS grid column-sizing token, e.g. `'minmax(140px, 2fr)'` or `'84px'`.
60
128
  * These values are joined to build the `grid-template-columns` style on the table.
@@ -70,6 +138,18 @@ export interface InlineColumnDef {
70
138
  * Defaults to `'any'` when omitted, allowing any decimal.
71
139
  */
72
140
  step?: string;
141
+ /**
142
+ * Custom cell-value resolver — takes precedence over the default read path.
143
+ * Use this for `type: 'readonly'` columns whose value is derived from master
144
+ * data or other cells (e.g. a standard yield looked up from the selected crop)
145
+ * and therefore does not live on the row object.
146
+ */
147
+ valueResolver?: (row: Record<string, unknown>) => unknown;
148
+ /**
149
+ * Per-row disabling of the cell widget (e.g. grey out the yield input when the
150
+ * selected crop does not permit yield input). Evaluated per row, keep it cheap.
151
+ */
152
+ disabledForRow?: (row: Record<string, unknown>) => boolean;
73
153
  /**
74
154
  * Sub-array navigation config — for columns whose value lives inside a nested array
75
155
  * on the row object (e.g. `ingredient[].quantity`).
@@ -90,6 +170,11 @@ export interface InlineColumnDef {
90
170
  arrayKeyField?: string;
91
171
  arrayKeyValue?: string;
92
172
  arrayValueField?: string;
173
+ /**
174
+ * Sub-rows editor config — required when `type: 'subrows'`, ignored otherwise.
175
+ * Drives the expandable key/percentage pair editor (see `InlineSubrowsConfig`).
176
+ */
177
+ subrows?: InlineSubrowsConfig;
93
178
  }
94
179
  /**
95
180
  * Full configuration for one inline-table instance — the "uischema" equivalent.
@@ -11,6 +11,8 @@ import { SuisseBilanzMetricsService } from './suissebilanz-metrics.service';
11
11
  */
12
12
  export declare class SuisseBilanzDashboardComponent {
13
13
  readonly embedded: import("@angular/core").InputSignal<boolean>;
14
+ /** Shows the official PDF download link even when `embedded` (header hidden). */
15
+ readonly showPdfButton: import("@angular/core").InputSignal<boolean>;
14
16
  protected readonly planning: PlanningBalanceService;
15
17
  private readonly sanitizer;
16
18
  protected readonly metricsService: SuisseBilanzMetricsService;
@@ -1,5 +1,7 @@
1
1
  import { PipeTransform, Signal } from '@angular/core';
2
2
  export type Locale = 'en' | 'de' | 'fr' | 'it';
3
+ /** Suffix of master-data `descriptor` designation fields (e.g. `designation_fra`) for a locale. */
4
+ export declare function designationSuffix(locale: Locale): 'deu' | 'eng' | 'fra' | 'ita';
3
5
  export type JsonFormsTranslator = (key: string, defaultMessage: string | undefined, context: unknown) => string;
4
6
  export declare class I18nService {
5
7
  private readonly _locale;
@@ -8,6 +10,7 @@ export declare class I18nService {
8
10
  readonly jsonFormsI18n: Signal<{
9
11
  locale: Locale;
10
12
  translate: JsonFormsTranslator;
13
+ translateError: (error: unknown, translate: JsonFormsTranslator) => string;
11
14
  }>;
12
15
  setLocale(locale: Locale): void;
13
16
  /** Returns true if the given string is a supported locale key. */
@@ -58,6 +58,11 @@ export declare class NavigationService {
58
58
  type: StorageEntryType;
59
59
  index: number;
60
60
  } | null>;
61
+ /** Selected field spreading row context (category/tab + row index) — used by validation deep-links */
62
+ readonly selectedFieldSpreadingDetail: import("@angular/core").WritableSignal<{
63
+ category: FieldSpreadingCategory;
64
+ index: number;
65
+ } | null>;
61
66
  /** True if currently viewing animal stock detail (not list) */
62
67
  readonly isAnimalStockDetailView: import("@angular/core").Signal<boolean>;
63
68
  /** True if currently on animal stock page (list or detail) */
@@ -81,6 +86,8 @@ export declare class NavigationService {
81
86
  navigateToFodderDetail(mode: FodderMode, index: number, category?: FodderCategory): void;
82
87
  navigateToFodderList(): void;
83
88
  navigateToFieldSpreading(category?: FieldSpreadingCategory): void;
89
+ /** Deep-link to a specific row of a field spreading tab (e.g. from a validation error). */
90
+ navigateToFieldSpreadingDetail(category: FieldSpreadingCategory, index: number): void;
84
91
  navigateToFieldCultivationDetail(tab: Exclude<FieldCultivationTab, 'general'>, index: number): void;
85
92
  navigateToFieldCultivationList(tab?: FieldCultivationTab): void;
86
93
  navigateToStorageDetail(type: StorageEntryType, index: number): void;
@@ -33,6 +33,14 @@ export declare class PlanningBalanceService {
33
33
  };
34
34
  readonly selectedEndpoint: import("@angular/core").Signal<PlanningEndpoint>;
35
35
  setSelectedEndpoint(endpoint: PlanningEndpoint): void;
36
+ /**
37
+ * Builds the exact payload posted to the planning API: calculationVersion stamped from
38
+ * the current metadata, `nutrientCycle.generateReport` forced to true (the PDF report
39
+ * must always be requested on submission), empty ingredient/routine slots stripped,
40
+ * null optional fields and host-only fields removed, custom display units converted
41
+ * to base units.
42
+ */
43
+ buildApiPayload(data: NaebiData): NaebiData;
36
44
  /** Fire a new planning-balance request with the current NAEBI data payload. */
37
45
  runPlanning(data: NaebiData, endpoint?: PlanningEndpoint): void;
38
46
  }
@@ -0,0 +1,39 @@
1
+ export interface CustomUnit {
2
+ /** Unit code stored on the row (e.g. 'ha', 't', 'dt'). */
3
+ code: string;
4
+ /** API base unit this custom unit converts to (e.g. 'm2', 'kg'). */
5
+ baseUnit: string;
6
+ /** Multiplier from the custom unit to its base unit (1 ha = 10'000 m2 → 10000). */
7
+ factor: number;
8
+ descriptor?: Record<string, unknown>;
9
+ }
10
+ export declare const CUSTOM_UNITS: readonly CustomUnit[];
11
+ /** True when the unit code is a custom display unit (not an API base unit). */
12
+ export declare function isCustomUnit(unit: unknown): boolean;
13
+ /** Base unit a quantity can be converted to: the custom unit's `baseUnit`, or the unit
14
+ * itself when it already is a base (or unknown) unit. */
15
+ export declare function baseUnitOf(unit: string | null | undefined): string | null | undefined;
16
+ /** Custom units convertible to the given base unit (e.g. 'kg' → [t, dt]). */
17
+ export declare function customUnitsForBaseUnit(baseUnit: string): CustomUnit[];
18
+ /**
19
+ * Builds a filter (compatible with the inline table's `optionsFilter`) keeping only the
20
+ * units compatible with the given context base unit(s) — i.e. each base unit plus its
21
+ * custom units. Pass a nullable base (e.g. derived from a product's matterState): `null`
22
+ * keeps the full list (nothing derived yet); pass an array of bases (e.g. ['kg', 'm3'])
23
+ * to pre-filter to their union before the exact base is known.
24
+ *
25
+ * Works on any entry shape exposing `id` (and `baseUnit` for custom units).
26
+ */
27
+ export declare function unitOptionsForBaseUnit<T extends {
28
+ id: string | number;
29
+ }>(contextBaseUnit: string | readonly string[] | null | undefined): (entry: T) => boolean;
30
+ /** Converts a quantity expressed in a custom unit to its base unit; pass-through for
31
+ * base/unknown units and null quantities. */
32
+ export declare function convertToBaseUnit(quantity: number | null | undefined, unit: string | null | undefined): {
33
+ quantity: number | null | undefined;
34
+ unit: string | null | undefined;
35
+ };
36
+ /** Converts a quantity between two units sharing the same base unit (kg ↔ t ↔ dt,
37
+ * m2 ↔ ha). Returns the quantity unchanged when either unit is missing or the units
38
+ * are incompatible (different base units). */
39
+ export declare function convertQuantityBetween(quantity: number | null | undefined, fromUnit: string | null | undefined, toUnit: string | null | undefined): number | null | undefined;
@@ -22,3 +22,25 @@ export declare function deepEqual(a: unknown, b: unknown): boolean;
22
22
  * @returns A deep copy of the value
23
23
  */
24
24
  export declare function deepClone<T>(value: T): T;
25
+ export declare function stripIncompleteIngredients<T>(value: T): T;
26
+ /**
27
+ * Sanitizes the planning payload: the calculation API (OpenAPI, no `nullable: true`
28
+ * anywhere) accepts an optional field as *absent* but rejects it when present with a
29
+ * null value ("must be string/number"). The working data model normalizes optional
30
+ * fields to null, so strip them at the submission boundary — mirroring what
31
+ * `stripIncompleteIngredients` does for ingredient slots.
32
+ *
33
+ * - inside `fieldCultivation` entry collections (fodderCropland, arableLand, special,
34
+ * feedingOfCropHarvest, dispenseOfCropHarvest): any null property is dropped
35
+ * (fieldName, userSpecificPlantName, yieldPerSquareMeasure, effectiveYield, …)
36
+ * - `nutrientCycle.fodder.trading.<collection>[].quantity`: null → dropped
37
+ * - `nutrientCycle.animalStock[].feeding.(winter|summer)Feeding`: never-filled routine
38
+ * slots (`{}` rows pre-created by the inline tables) and null fields are dropped;
39
+ * a season — and `feeding` itself — is removed when nothing filled-in remains
40
+ * - `nutrientCycle.fieldSpreading`: null fields dropped (`soilPh`, `slurry.*`,
41
+ * `manure.*`); `slurry`/`manure` removed when nothing filled-in remains
42
+ */
43
+ export declare function sanitizePlanningPayload(data: NaebiDataLike): NaebiDataLike;
44
+ /** Structural type so object.util stays free of a NaebiData import (circular-safe). */
45
+ type NaebiDataLike = Record<string, unknown>;
46
+ export {};
@@ -9,6 +9,7 @@ import Ajv from 'ajv';
9
9
  *
10
10
  * Custom keywords:
11
11
  * - `permittedStableSystem`: Validates stable system against animal category constraints
12
+ * - `proportionSum`: Validates the sum of one numeric field across array items
12
13
  *
13
14
  * @param options - Additional AJV options to merge with defaults
14
15
  */
@@ -16,3 +16,9 @@ export declare function derivePermittedStableSystems(animalCategoryPRIF: number
16
16
  * Maps animalCategoryPRIF -> array of permitted stableSystem enum strings.
17
17
  */
18
18
  export declare const PERMITTED_STABLE_SYSTEMS: Record<number, string[]>;
19
+ /**
20
+ * Pre-computed map of non-zero per-category minimum yields (yieldPerSquareMeasure).
21
+ * Only 8 of the 314 crop categories declare a minimum > 0 (e.g. cereals, peas);
22
+ * all others fall back to the schema-level `minimum: 0`.
23
+ */
24
+ export declare const CROP_MINIMUM_YIELD_PER_SQUARE_MEASURE: Record<string, number>;