Skip to content

Commit 892780b

Browse files
committed
Refactor product models, payloads and workspace
Introduce comprehensive product domain models (status, suppliers, tags, inventories, pricing, OEM, equivalents, vehicle applications, etc.), add entity-reference and initialProductPayload. Replace legacy ProductRequest/Response shapes with UpdateProductPayload, mapResponseToPayload and defaultUpdatePayload. Add WarehouseService and adjust CategoryService/ProductService signatures. Refactor product workspace and form flow to separate API Product (product) from editable payload (productForm), update many components/templates to use new fields and fix pagination/slicing and inventory aggregation. Misc UI/behavior adjustments and commented legacy transformer code left for reference. Verify API compatibility when saving/updating products.
1 parent 260409d commit 892780b

32 files changed

Lines changed: 906 additions & 919 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface ProductAdditionalCodeSummary {
2+
id: string;
3+
code: string;
4+
active: boolean;
5+
}

src/app/core/models/catetories/category-options.model.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
export interface CategoryOptionsResponse {
2-
success: boolean;
3-
message: string;
4-
data: CategoryOption[];
5-
}
1+
// export interface CategoryOptionsResponse {
2+
// success: boolean;
3+
// message: string;
4+
// data: CategoryOption[];
5+
// }
66

77
export interface CategoryOption {
88
id: string;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { EntityReference } from '../generals/entity-reference';
2+
3+
export interface ProductEquivalent {
4+
id: string;
5+
observation: string | null;
6+
product: EntityReference;
7+
equivalent_product: EntityReference;
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface EntityReference {
2+
id: string;
3+
name: string;
4+
slug?: string;
5+
icon?: string;
6+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export interface ProductInventory {
2+
id: string;
3+
quantity: number;
4+
reserved_quantity: number;
5+
available_quantity?: number;
6+
minimum_quantity: number;
7+
maximum_quantity: number;
8+
allow_backorder: boolean;
9+
active: boolean;
10+
warehouse: {
11+
id: string;
12+
name: string;
13+
code: string | null;
14+
};
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export interface ProductNote {
2+
id: string;
3+
note: string;
4+
type: string;
5+
active: boolean;
6+
description: string;
7+
date: string;
8+
author: string;
9+
status: string;
10+
created_at: string;
11+
updated_at: string;
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { EntityReference } from '../generals/entity-reference';
2+
3+
export interface ProductOemCode {
4+
id: string;
5+
oem_code: string;
6+
manufacturer?: EntityReference;
7+
product?: EntityReference;
8+
created_at: string;
9+
updated_at: string;
10+
is_primary: boolean;
11+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface ProductPartOrigin {
2+
id: string;
3+
name: string;
4+
description: string | null;
5+
display_order: number;
6+
active: boolean;
7+
created_at: string;
8+
updated_at: string;
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface ProductPricing {
2+
regular_price: number;
3+
promotional_price: number | null;
4+
current_price: number;
5+
is_on_promotion: boolean;
6+
promotion_start: string | null;
7+
promotion_end: string | null;
8+
active: boolean;
9+
}

src/app/core/models/products/product-request.model.ts

Lines changed: 159 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -55,69 +55,188 @@ export interface ProductStatus {
5555

5656
export type CommercialStatus = 'pending' | 'approved' | 'rejected';
5757

58-
// PARA REMOVER FUTURAMENTE APÓS UPDATE
59-
60-
export interface ProductPricePayload {
61-
price: number;
62-
promotional_price?: number | null;
63-
promotion_start?: string | null;
64-
promotion_end?: string | null;
65-
}
66-
67-
export interface ProductInventoryPayload {
68-
warehouse_id: string | null;
69-
70-
quantity: number;
71-
72-
minimum_quantity?: number;
73-
maximum_quantity?: number | null;
74-
75-
allow_backorder?: boolean;
76-
}
77-
58+
/**
59+
* Payload utilizado para atualização parcial de um produto.
60+
*
61+
* Os campos são opcionais porque o backend utiliza a regra
62+
* `sometimes` no UpdateProductRequest.
63+
*
64+
* Importante:
65+
* - `undefined` significa que o campo não será enviado.
66+
* - `null` é utilizado somente nos campos em que o backend
67+
* permite explicitamente valor nulo.
68+
* - Arrays vazios devem ser enviados quando a intenção for
69+
* persistir uma coleção vazia.
70+
*/
7871
export interface UpdateProductPayload {
79-
category_id?: string | null;
80-
manufacturer_id?: string | null;
72+
category_id?: string;
73+
manufacturer_id?: string;
8174
part_origin_id?: string | null;
8275
unit_id?: string | null;
76+
status_id?: string;
77+
78+
oem_code_ids?: string[];
79+
product_code_ids?: string[];
80+
equivalent_product_ids?: string[];
81+
application_ids?: string[];
82+
supplier_ids?: string[];
83+
tag_ids?: string[];
84+
icon?: string | null;
8385

84-
internal_code?: string | null;
86+
internal_code?: string;
8587
barcode?: string | null;
86-
name?: string | null;
87-
slug?: string | null;
8888

89-
icon?: string | null;
90-
image?: string | null;
89+
name?: string;
90+
slug?: string;
9191

9292
short_description?: string | null;
93-
description?: string | null;
93+
description?: string;
9494

95-
weight?: number | null;
96-
height?: number | null;
97-
width?: number | null;
98-
length?: number | null;
95+
weight?: number;
96+
height?: number;
97+
width?: number;
98+
length?: number;
9999

100-
unit?: string | null;
101-
102-
active?: boolean;
103100
featured?: boolean;
101+
active?: boolean;
104102

105103
price?: UpdateProductPricePayload;
106104

107-
inventory?: UpdateProductInventoryPayload;
105+
inventories?: UpdateProductInventoryPayload[];
108106
}
109107

108+
/**
109+
* Dados comerciais utilizados na atualização do produto.
110+
*/
110111
export interface UpdateProductPricePayload {
111-
price?: number | null;
112+
price?: number;
112113
promotional_price?: number | null;
113114
promotion_start?: string | null;
114115
promotion_end?: string | null;
115116
}
116117

118+
/**
119+
* Estoque de um produto por depósito.
120+
*
121+
* O backend aceita múltiplos registros em `inventories`.
122+
*/
117123
export interface UpdateProductInventoryPayload {
118-
warehouse_id?: string | null;
119-
quantity?: number | null;
124+
id?: string | null;
125+
warehouse_id: string;
126+
quantity: number;
120127
minimum_quantity?: number | null;
121128
maximum_quantity?: number | null;
122-
allow_backorder?: boolean;
129+
allow_backorder?: boolean | null;
130+
active?: boolean | null;
131+
}
132+
133+
/**
134+
* Payload vazio para inicializar o sinal `productForm` antes de carregar um produto.
135+
*/
136+
export function defaultUpdatePayload(): UpdateProductPayload {
137+
return {};
138+
}
139+
140+
/**
141+
* Converte a resposta da API (ProductResponse) no payload de atualização (UpdateProductPayload).
142+
*
143+
* Campos escalares são sempre mapeados.
144+
* Campos relacionais (oem_code_ids, tag_ids, etc.) são mantidos como `undefined`:
145+
* o backend ignora chaves ausentes (regra `array_key_exists`), portanto não haverá
146+
* alteração acidental nos relacionamentos ao salvar sem editar as abas correspondentes.
147+
* Cada aba de edição relacional é responsável por incluir o array correto no payload.
148+
*/
149+
export function mapResponseToPayload(prod: {
150+
category?: { id: string } | null;
151+
manufacturer?: { id: string } | null;
152+
part_origin?: { id: string } | null;
153+
unit?: { id: string } | null;
154+
status?: { id: string } | null;
155+
internal_code: string;
156+
barcode?: string | null;
157+
name: string;
158+
slug: string;
159+
short_description?: string | null;
160+
description: string;
161+
weight: string | number;
162+
height: string | number;
163+
width: string | number;
164+
length: string | number;
165+
featured: boolean;
166+
active?: boolean | null;
167+
pricing?: {
168+
regular_price?: number;
169+
promotional_price?: number | null;
170+
promotion_start?: string | null;
171+
promotion_end?: string | null;
172+
} | null;
173+
inventories?: {
174+
id?: string;
175+
warehouse: { id: string };
176+
quantity: number;
177+
minimum_quantity?: number | null;
178+
maximum_quantity?: number | null;
179+
allow_backorder?: boolean;
180+
active?: boolean;
181+
}[];
182+
}): UpdateProductPayload {
183+
const firstInventory = prod.inventories?.[0];
184+
185+
return {
186+
// Referências (IDs de entidades relacionadas)
187+
category_id: prod.category?.id,
188+
manufacturer_id: prod.manufacturer?.id,
189+
part_origin_id: prod.part_origin?.id ?? null,
190+
unit_id: prod.unit?.id ?? null,
191+
status_id: prod.status?.id,
192+
193+
// Campos escalares
194+
internal_code: prod.internal_code,
195+
barcode: prod.barcode ?? null,
196+
name: prod.name,
197+
slug: prod.slug,
198+
short_description: prod.short_description ?? null,
199+
description: prod.description,
200+
201+
// Dimensões (API retorna como string, payload espera number)
202+
weight: parseFloat(String(prod.weight)) || 0,
203+
height: parseFloat(String(prod.height)) || 0,
204+
width: parseFloat(String(prod.width)) || 0,
205+
length: parseFloat(String(prod.length)) || 0,
206+
207+
featured: prod.featured,
208+
active: prod.active ?? false,
209+
210+
// Preço: incluído apenas se existir no produto
211+
price: prod.pricing
212+
? {
213+
price: prod.pricing.regular_price,
214+
promotional_price: prod.pricing.promotional_price ?? null,
215+
promotion_start: prod.pricing.promotion_start ?? null,
216+
promotion_end: prod.pricing.promotion_end ?? null,
217+
}
218+
: undefined,
219+
220+
// Inventário: incluído apenas se o produto já tiver estoque cadastrado
221+
inventories: firstInventory
222+
? [
223+
{
224+
warehouse_id: firstInventory.warehouse.id,
225+
quantity: firstInventory.quantity,
226+
minimum_quantity: firstInventory.minimum_quantity ?? null,
227+
maximum_quantity: firstInventory.maximum_quantity ?? null,
228+
allow_backorder: firstInventory.allow_backorder ?? false,
229+
active: firstInventory.active ?? true,
230+
},
231+
]
232+
: undefined,
233+
234+
// Relacionamentos: undefined (não enviados) até o usuário editar a aba correspondente.
235+
// oem_code_ids: undefined,
236+
// product_code_ids: undefined,
237+
// equivalent_product_ids: undefined,
238+
// application_ids: undefined,
239+
// supplier_ids: undefined,
240+
// tag_ids: undefined,
241+
};
123242
}

0 commit comments

Comments
 (0)