backbone_catalog/domain/entity/
attribute_value.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5
6use super::CatalogStatus;
7use super::AuditMetadata;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct AttributeValueId(pub Uuid);
13
14impl AttributeValueId {
15 pub fn new(id: Uuid) -> Self { Self(id) }
16 pub fn generate() -> Self { Self(Uuid::new_v4()) }
17 pub fn into_inner(self) -> Uuid { self.0 }
18}
19
20impl std::fmt::Display for AttributeValueId {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 write!(f, "{}", self.0)
23 }
24}
25
26impl std::str::FromStr for AttributeValueId {
27 type Err = uuid::Error;
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 Ok(Self(Uuid::parse_str(s)?))
30 }
31}
32
33impl From<Uuid> for AttributeValueId {
34 fn from(id: Uuid) -> Self { Self(id) }
35}
36
37impl From<AttributeValueId> for Uuid {
38 fn from(id: AttributeValueId) -> Self { id.0 }
39}
40
41impl AsRef<Uuid> for AttributeValueId {
42 fn as_ref(&self) -> &Uuid { &self.0 }
43}
44
45impl std::ops::Deref for AttributeValueId {
46 type Target = Uuid;
47 fn deref(&self) -> &Self::Target { &self.0 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
51pub struct AttributeValue {
52 pub id: Uuid,
53 pub attribute_id: Uuid,
54 pub code: String,
55 pub label: String,
56 pub label_en: Option<String>,
57 pub swatch_hex: Option<String>,
58 pub icon: Option<String>,
59 pub sort_order: i32,
60 pub status: CatalogStatus,
61 #[serde(default)]
62 #[sqlx(json)]
63 pub metadata: AuditMetadata,
64}
65
66impl AttributeValue {
67 pub fn builder() -> AttributeValueBuilder {
69 <AttributeValueBuilder as Default>::default()
70 }
71
72 pub fn new(attribute_id: Uuid, code: String, label: String, sort_order: i32, status: CatalogStatus) -> Self {
74 Self {
75 id: Uuid::new_v4(),
76 attribute_id,
77 code,
78 label,
79 label_en: None,
80 swatch_hex: None,
81 icon: None,
82 sort_order,
83 status,
84 metadata: AuditMetadata::default(),
85 }
86 }
87
88 pub fn id(&self) -> &Uuid {
90 &self.id
91 }
92
93 pub fn typed_id(&self) -> AttributeValueId {
95 AttributeValueId(self.id)
96 }
97
98 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
100 self.metadata.created_at.as_ref()
101 }
102
103 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
105 self.metadata.updated_at.as_ref()
106 }
107
108 pub fn is_deleted(&self) -> bool {
110 self.metadata.deleted_at.is_some()
111 }
112
113 pub fn is_active(&self) -> bool {
115 self.metadata.deleted_at.is_none()
116 }
117
118 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
120 self.metadata.deleted_at.as_ref()
121 }
122
123 pub fn created_by(&self) -> Option<&Uuid> {
125 self.metadata.created_by.as_ref()
126 }
127
128 pub fn updated_by(&self) -> Option<&Uuid> {
130 self.metadata.updated_by.as_ref()
131 }
132
133 pub fn deleted_by(&self) -> Option<&Uuid> {
135 self.metadata.deleted_by.as_ref()
136 }
137
138 pub fn status(&self) -> &CatalogStatus {
140 &self.status
141 }
142
143
144 pub fn with_label_en(mut self, value: String) -> Self {
150 self.label_en = Some(value);
151 self
152 }
153
154 pub fn with_swatch_hex(mut self, value: String) -> Self {
156 self.swatch_hex = Some(value);
157 self
158 }
159
160 pub fn with_icon(mut self, value: String) -> Self {
162 self.icon = Some(value);
163 self
164 }
165
166 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
172 for (key, value) in fields {
173 match key.as_str() {
174 "attribute_id" => {
175 if let Ok(v) = serde_json::from_value(value) { self.attribute_id = v; }
176 }
177 "code" => {
178 if let Ok(v) = serde_json::from_value(value) { self.code = v; }
179 }
180 "label" => {
181 if let Ok(v) = serde_json::from_value(value) { self.label = v; }
182 }
183 "label_en" => {
184 if let Ok(v) = serde_json::from_value(value) { self.label_en = v; }
185 }
186 "swatch_hex" => {
187 if let Ok(v) = serde_json::from_value(value) { self.swatch_hex = v; }
188 }
189 "icon" => {
190 if let Ok(v) = serde_json::from_value(value) { self.icon = v; }
191 }
192 "sort_order" => {
193 if let Ok(v) = serde_json::from_value(value) { self.sort_order = v; }
194 }
195 "status" => {
196 if let Ok(v) = serde_json::from_value(value) { self.status = v; }
197 }
198 _ => {} }
200 }
201 }
202
203 }
206
207impl super::Entity for AttributeValue {
208 type Id = Uuid;
209
210 fn entity_id(&self) -> &Self::Id {
211 &self.id
212 }
213
214 fn entity_type() -> &'static str {
215 "AttributeValue"
216 }
217}
218
219impl backbone_core::PersistentEntity for AttributeValue {
220 fn entity_id(&self) -> String {
221 self.id.to_string()
222 }
223 fn set_entity_id(&mut self, id: String) {
224 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
225 self.id = uuid;
226 }
227 }
228 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
229 self.metadata.created_at
230 }
231 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
232 self.metadata.created_at = Some(ts);
233 }
234 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
235 self.metadata.updated_at
236 }
237 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
238 self.metadata.updated_at = Some(ts);
239 }
240 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
241 self.metadata.deleted_at
242 }
243 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
244 self.metadata.deleted_at = ts;
245 }
246}
247
248impl backbone_orm::EntityRepoMeta for AttributeValue {
249 fn column_types() -> std::collections::HashMap<String, String> {
250 let mut m = std::collections::HashMap::new();
251 m.insert("id".to_string(), "uuid".to_string());
252 m.insert("attribute_id".to_string(), "uuid".to_string());
253 m.insert("status".to_string(), "catalog_status".to_string());
254 m
255 }
256 fn search_fields() -> &'static [&'static str] {
257 &["code", "label"]
258 }
259 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
260 &[("attribute", "attributes", "attributeId")]
261 }
262}
263
264#[derive(Debug, Clone, Default)]
269pub struct AttributeValueBuilder {
270 attribute_id: Option<Uuid>,
271 code: Option<String>,
272 label: Option<String>,
273 label_en: Option<String>,
274 swatch_hex: Option<String>,
275 icon: Option<String>,
276 sort_order: Option<i32>,
277 status: Option<CatalogStatus>,
278}
279
280impl AttributeValueBuilder {
281 pub fn attribute_id(mut self, value: Uuid) -> Self {
283 self.attribute_id = Some(value);
284 self
285 }
286
287 pub fn code(mut self, value: String) -> Self {
289 self.code = Some(value);
290 self
291 }
292
293 pub fn label(mut self, value: String) -> Self {
295 self.label = Some(value);
296 self
297 }
298
299 pub fn label_en(mut self, value: String) -> Self {
301 self.label_en = Some(value);
302 self
303 }
304
305 pub fn swatch_hex(mut self, value: String) -> Self {
307 self.swatch_hex = Some(value);
308 self
309 }
310
311 pub fn icon(mut self, value: String) -> Self {
313 self.icon = Some(value);
314 self
315 }
316
317 pub fn sort_order(mut self, value: i32) -> Self {
319 self.sort_order = Some(value);
320 self
321 }
322
323 pub fn status(mut self, value: CatalogStatus) -> Self {
325 self.status = Some(value);
326 self
327 }
328
329 pub fn build(self) -> Result<AttributeValue, String> {
333 let attribute_id = self.attribute_id.ok_or_else(|| "attribute_id is required".to_string())?;
334 let code = self.code.ok_or_else(|| "code is required".to_string())?;
335 let label = self.label.ok_or_else(|| "label is required".to_string())?;
336
337 Ok(AttributeValue {
338 id: Uuid::new_v4(),
339 attribute_id,
340 code,
341 label,
342 label_en: self.label_en,
343 swatch_hex: self.swatch_hex,
344 icon: self.icon,
345 sort_order: self.sort_order.unwrap_or(0),
346 status: self.status.unwrap_or_default(),
347 metadata: AuditMetadata::default(),
348 })
349 }
350}