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}
204
205impl super::Entity for AttributeValue {
206 type Id = Uuid;
207
208 fn entity_id(&self) -> &Self::Id {
209 &self.id
210 }
211
212 fn entity_type() -> &'static str {
213 "AttributeValue"
214 }
215}
216
217impl backbone_core::PersistentEntity for AttributeValue {
218 fn entity_id(&self) -> String {
219 self.id.to_string()
220 }
221 fn set_entity_id(&mut self, id: String) {
222 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
223 self.id = uuid;
224 }
225 }
226 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
227 self.metadata.created_at
228 }
229 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
230 self.metadata.created_at = Some(ts);
231 }
232 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
233 self.metadata.updated_at
234 }
235 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
236 self.metadata.updated_at = Some(ts);
237 }
238 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
239 self.metadata.deleted_at
240 }
241 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
242 self.metadata.deleted_at = ts;
243 }
244}
245
246impl backbone_orm::EntityRepoMeta for AttributeValue {
247 fn column_types() -> std::collections::HashMap<String, String> {
248 let mut m = std::collections::HashMap::new();
249 m.insert("id".to_string(), "uuid".to_string());
250 m.insert("attribute_id".to_string(), "uuid".to_string());
251 m.insert("status".to_string(), "catalog_status".to_string());
252 m
253 }
254 fn search_fields() -> &'static [&'static str] {
255 &["code", "label"]
256 }
257 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
258 &[("attribute", "attributes", "attributeId")]
259 }
260}
261
262#[derive(Debug, Clone, Default)]
267pub struct AttributeValueBuilder {
268 attribute_id: Option<Uuid>,
269 code: Option<String>,
270 label: Option<String>,
271 label_en: Option<String>,
272 swatch_hex: Option<String>,
273 icon: Option<String>,
274 sort_order: Option<i32>,
275 status: Option<CatalogStatus>,
276}
277
278impl AttributeValueBuilder {
279 pub fn attribute_id(mut self, value: Uuid) -> Self {
281 self.attribute_id = Some(value);
282 self
283 }
284
285 pub fn code(mut self, value: String) -> Self {
287 self.code = Some(value);
288 self
289 }
290
291 pub fn label(mut self, value: String) -> Self {
293 self.label = Some(value);
294 self
295 }
296
297 pub fn label_en(mut self, value: String) -> Self {
299 self.label_en = Some(value);
300 self
301 }
302
303 pub fn swatch_hex(mut self, value: String) -> Self {
305 self.swatch_hex = Some(value);
306 self
307 }
308
309 pub fn icon(mut self, value: String) -> Self {
311 self.icon = Some(value);
312 self
313 }
314
315 pub fn sort_order(mut self, value: i32) -> Self {
317 self.sort_order = Some(value);
318 self
319 }
320
321 pub fn status(mut self, value: CatalogStatus) -> Self {
323 self.status = Some(value);
324 self
325 }
326
327 pub fn build(self) -> Result<AttributeValue, String> {
331 let attribute_id = self.attribute_id.ok_or_else(|| "attribute_id is required".to_string())?;
332 let code = self.code.ok_or_else(|| "code is required".to_string())?;
333 let label = self.label.ok_or_else(|| "label is required".to_string())?;
334
335 Ok(AttributeValue {
336 id: Uuid::new_v4(),
337 attribute_id,
338 code,
339 label,
340 label_en: self.label_en,
341 swatch_hex: self.swatch_hex,
342 icon: self.icon,
343 sort_order: self.sort_order.unwrap_or(0),
344 status: self.status.unwrap_or_default(),
345 metadata: AuditMetadata::default(),
346 })
347 }
348}