1use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc};
11
12#[cfg(feature = "openapi")]
13#[cfg(feature = "openapi")]
14use utoipa::ToSchema;
15
16#[cfg(feature = "validation")]
17use validator::Validate;
18
19use crate::domain::entity::AttributeValue;
20use crate::domain::entity::AuditMetadata;
21use crate::domain::entity::CatalogStatus;
22
23#[derive(Debug, Clone, Deserialize)]
32#[cfg_attr(feature = "openapi", derive(ToSchema))]
33#[cfg_attr(feature = "validation", derive(Validate))]
34#[serde(rename_all = "camelCase")]
35pub struct CreateAttributeValueDto {
36 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
37 #[serde(alias = "attribute_id")]
38 pub attribute_id: Uuid,
39 #[cfg_attr(feature = "validation", validate(length(max = 40)))]
40 #[cfg_attr(feature = "openapi", schema(example = "example"))]
41 pub code: String,
42 #[cfg_attr(feature = "validation", validate(length(max = 100)))]
43 #[cfg_attr(feature = "openapi", schema(example = "example"))]
44 pub label: String,
45 #[cfg_attr(feature = "validation", validate(length(max = 100)))]
46 #[serde(default, skip_serializing_if = "Option::is_none", alias = "label_en")]
47 pub label_en: Option<String>,
48 #[cfg_attr(feature = "validation", validate(length(max = 10)))]
49 #[serde(default, skip_serializing_if = "Option::is_none", alias = "swatch_hex")]
50 pub swatch_hex: Option<String>,
51 #[cfg_attr(feature = "validation", validate(length(max = 255)))]
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub icon: Option<String>,
54 #[cfg_attr(feature = "openapi", schema(example = 42))]
55 #[serde(alias = "sort_order")]
56 pub sort_order: i32,
57 pub status: CatalogStatus,
58}
59
60#[derive(Debug, Clone, Deserialize)]
69#[cfg_attr(feature = "openapi", derive(ToSchema))]
70#[cfg_attr(feature = "validation", derive(Validate))]
71#[serde(rename_all = "camelCase")]
72pub struct UpdateAttributeValueDto {
73 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
74 #[serde(alias = "attribute_id")]
75 pub attribute_id: Uuid,
76 #[cfg_attr(feature = "validation", validate(length(max = 40)))]
77 #[cfg_attr(feature = "openapi", schema(example = "example"))]
78 pub code: String,
79 #[cfg_attr(feature = "validation", validate(length(max = 100)))]
80 #[cfg_attr(feature = "openapi", schema(example = "example"))]
81 pub label: String,
82 #[cfg_attr(feature = "validation", validate(length(max = 100)))]
83 #[serde(default, skip_serializing_if = "Option::is_none", alias = "label_en")]
84 pub label_en: Option<String>,
85 #[cfg_attr(feature = "validation", validate(length(max = 10)))]
86 #[serde(default, skip_serializing_if = "Option::is_none", alias = "swatch_hex")]
87 pub swatch_hex: Option<String>,
88 #[cfg_attr(feature = "validation", validate(length(max = 255)))]
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub icon: Option<String>,
91 #[cfg_attr(feature = "openapi", schema(example = 42))]
92 #[serde(alias = "sort_order")]
93 pub sort_order: i32,
94 pub status: CatalogStatus,
95}
96
97#[derive(Debug, Clone, Default, Deserialize)]
106#[cfg_attr(feature = "openapi", derive(ToSchema))]
107#[cfg_attr(feature = "validation", derive(Validate))]
108#[serde(rename_all = "camelCase")]
109pub struct PatchAttributeValueDto {
110 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
111 #[serde(skip_serializing_if = "Option::is_none", alias = "attribute_id")]
112 pub attribute_id: Option<Uuid>,
113 #[cfg_attr(feature = "validation", validate(length(max = 40)))]
114 #[cfg_attr(feature = "openapi", schema(example = "example"))]
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub code: Option<String>,
117 #[cfg_attr(feature = "validation", validate(length(max = 100)))]
118 #[cfg_attr(feature = "openapi", schema(example = "example"))]
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub label: Option<String>,
121 #[cfg_attr(feature = "validation", validate(length(max = 100)))]
122 #[serde(skip_serializing_if = "Option::is_none", alias = "label_en")]
123 pub label_en: Option<String>,
124 #[cfg_attr(feature = "validation", validate(length(max = 10)))]
125 #[serde(skip_serializing_if = "Option::is_none", alias = "swatch_hex")]
126 pub swatch_hex: Option<String>,
127 #[cfg_attr(feature = "validation", validate(length(max = 255)))]
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub icon: Option<String>,
130 #[cfg_attr(feature = "openapi", schema(example = 42))]
131 #[serde(skip_serializing_if = "Option::is_none", alias = "sort_order")]
132 pub sort_order: Option<i32>,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub status: Option<CatalogStatus>,
135}
136
137impl PatchAttributeValueDto {
138 pub fn has_changes(&self) -> bool {
140 self.attribute_id.is_some() || self.code.is_some() || self.label.is_some() || self.label_en.is_some() || self.swatch_hex.is_some() || self.icon.is_some() || self.sort_order.is_some() || self.status.is_some()
141 }
142}
143
144#[derive(Debug, Clone, Serialize)]
153#[cfg_attr(feature = "openapi", derive(ToSchema))]
154#[serde(rename_all = "camelCase")]
155pub struct AttributeValueResponseDto {
156 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
157 pub id: Uuid,
158 #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
159 pub attribute_id: Uuid,
160 #[cfg_attr(feature = "openapi", schema(example = "example"))]
161 pub code: String,
162 #[cfg_attr(feature = "openapi", schema(example = "example"))]
163 pub label: String,
164 pub label_en: Option<String>,
165 pub swatch_hex: Option<String>,
166 pub icon: Option<String>,
167 #[cfg_attr(feature = "openapi", schema(example = 42))]
168 pub sort_order: i32,
169 pub status: CatalogStatus,
170 pub metadata: AuditMetadata,
171}
172
173#[derive(Debug, Clone, Serialize)]
182#[cfg_attr(feature = "openapi", derive(ToSchema))]
183#[serde(rename_all = "camelCase")]
184pub struct AttributeValueListResponseDto {
185 pub items: Vec<AttributeValueResponseDto>,
187 pub total: u64,
189 pub page: u32,
191 pub per_page: u32,
193 pub total_pages: u32,
195 pub has_next: bool,
197 pub has_prev: bool,
199}
200
201impl AttributeValueListResponseDto {
202 pub fn new(items: Vec<AttributeValueResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
204 let total_pages = if per_page > 0 {
205 ((total as f64) / (per_page as f64)).ceil() as u32
206 } else {
207 0
208 };
209 Self {
210 items,
211 total,
212 page,
213 per_page,
214 total_pages,
215 has_next: page < total_pages,
216 has_prev: page > 1,
217 }
218 }
219}
220
221#[derive(Debug, Clone, Serialize)]
223#[cfg_attr(feature = "openapi", derive(ToSchema))]
224#[serde(rename_all = "camelCase")]
225pub struct AttributeValueSummaryDto {
226 pub id: Uuid,
227 pub attribute_id: Uuid,
228 pub code: String,
229 pub label: String,
230 pub created_at: Option<DateTime<Utc>>,
231}
232
233impl From<AttributeValue> for AttributeValueResponseDto {
238 fn from(entity: AttributeValue) -> Self {
239 Self {
240 id: entity.id,
241 attribute_id: entity.attribute_id,
242 code: entity.code,
243 label: entity.label,
244 label_en: entity.label_en,
245 swatch_hex: entity.swatch_hex,
246 icon: entity.icon,
247 sort_order: entity.sort_order,
248 status: entity.status,
249 metadata: entity.metadata,
250 }
251 }
252}
253
254impl From<AttributeValue> for AttributeValueSummaryDto {
255 fn from(entity: AttributeValue) -> Self {
256 let created_at = backbone_core::PersistentEntity::created_at(&entity);
257 Self {
258 id: entity.id,
259 attribute_id: entity.attribute_id,
260 code: entity.code,
261 label: entity.label,
262 created_at,
263 }
264 }
265}
266
267impl From<CreateAttributeValueDto> for AttributeValue {
268 fn from(dto: CreateAttributeValueDto) -> Self {
269 Self {
270 id: Uuid::new_v4(),
271 attribute_id: dto.attribute_id,
272 code: dto.code,
273 label: dto.label,
274 label_en: dto.label_en,
275 swatch_hex: dto.swatch_hex,
276 icon: dto.icon,
277 sort_order: dto.sort_order,
278 status: dto.status,
279 metadata: AuditMetadata::default(),
280 }
281 }
282}
283
284impl From<&AttributeValue> for AttributeValueResponseDto {
285 fn from(entity: &AttributeValue) -> Self {
286 Self {
287 id: entity.id.clone(),
288 attribute_id: entity.attribute_id.clone(),
289 code: entity.code.clone(),
290 label: entity.label.clone(),
291 label_en: entity.label_en.clone(),
292 swatch_hex: entity.swatch_hex.clone(),
293 icon: entity.icon.clone(),
294 sort_order: entity.sort_order.clone(),
295 status: entity.status.clone(),
296 metadata: entity.metadata.clone(),
297 }
298 }
299}
300
301impl backbone_core::FromCreateDto<CreateAttributeValueDto> for AttributeValue {
302 fn from_create_dto(dto: CreateAttributeValueDto) -> backbone_core::ServiceResult<Self> {
303 Ok(AttributeValue::from(dto))
304 }
305}
306
307impl backbone_core::ApplyUpdateDto<UpdateAttributeValueDto> for AttributeValue {
308 fn apply_update(mut self, dto: UpdateAttributeValueDto) -> backbone_core::ServiceResult<Self> {
309 self.attribute_id = dto.attribute_id;
310 self.code = dto.code;
311 self.label = dto.label;
312 self.label_en = dto.label_en;
313 self.swatch_hex = dto.swatch_hex;
314 self.icon = dto.icon;
315 self.sort_order = dto.sort_order;
316 self.status = dto.status;
317 Ok(self)
318 }
319}
320
321