backbone_pos/domain/entity/
pos_closing_entry.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::PosClosingStatus;
8use super::AuditMetadata;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct PosClosingEntryId(pub Uuid);
14
15impl PosClosingEntryId {
16 pub fn new(id: Uuid) -> Self { Self(id) }
17 pub fn generate() -> Self { Self(Uuid::new_v4()) }
18 pub fn into_inner(self) -> Uuid { self.0 }
19}
20
21impl std::fmt::Display for PosClosingEntryId {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{}", self.0)
24 }
25}
26
27impl std::str::FromStr for PosClosingEntryId {
28 type Err = uuid::Error;
29 fn from_str(s: &str) -> Result<Self, Self::Err> {
30 Ok(Self(Uuid::parse_str(s)?))
31 }
32}
33
34impl From<Uuid> for PosClosingEntryId {
35 fn from(id: Uuid) -> Self { Self(id) }
36}
37
38impl From<PosClosingEntryId> for Uuid {
39 fn from(id: PosClosingEntryId) -> Self { id.0 }
40}
41
42impl AsRef<Uuid> for PosClosingEntryId {
43 fn as_ref(&self) -> &Uuid { &self.0 }
44}
45
46impl std::ops::Deref for PosClosingEntryId {
47 type Target = Uuid;
48 fn deref(&self) -> &Self::Target { &self.0 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
52pub struct PosClosingEntry {
53 pub id: Uuid,
54 pub pos_profile_id: Uuid,
55 pub opening_entry_id: Uuid,
56 pub closed_at: DateTime<Utc>,
57 pub cashier_party_id: Uuid,
58 pub totals_by_method: Option<serde_json::Value>,
59 pub grand_total: Decimal,
60 pub invoice_count: i32,
61 pub difference_total: Decimal,
62 pub status: PosClosingStatus,
63 #[serde(default)]
64 #[sqlx(json)]
65 pub metadata: AuditMetadata,
66}
67
68impl PosClosingEntry {
69 pub fn builder() -> PosClosingEntryBuilder {
71 <PosClosingEntryBuilder as Default>::default()
72 }
73
74 pub fn new(pos_profile_id: Uuid, opening_entry_id: Uuid, closed_at: DateTime<Utc>, cashier_party_id: Uuid, grand_total: Decimal, invoice_count: i32, difference_total: Decimal, status: PosClosingStatus) -> Self {
76 Self {
77 id: Uuid::new_v4(),
78 pos_profile_id,
79 opening_entry_id,
80 closed_at,
81 cashier_party_id,
82 totals_by_method: None,
83 grand_total,
84 invoice_count,
85 difference_total,
86 status,
87 metadata: AuditMetadata::default(),
88 }
89 }
90
91 pub fn id(&self) -> &Uuid {
93 &self.id
94 }
95
96 pub fn typed_id(&self) -> PosClosingEntryId {
98 PosClosingEntryId(self.id)
99 }
100
101 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
103 self.metadata.created_at.as_ref()
104 }
105
106 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
108 self.metadata.updated_at.as_ref()
109 }
110
111 pub fn is_deleted(&self) -> bool {
113 self.metadata.deleted_at.is_some()
114 }
115
116 pub fn is_active(&self) -> bool {
118 self.metadata.deleted_at.is_none()
119 }
120
121 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
123 self.metadata.deleted_at.as_ref()
124 }
125
126 pub fn created_by(&self) -> Option<&Uuid> {
128 self.metadata.created_by.as_ref()
129 }
130
131 pub fn updated_by(&self) -> Option<&Uuid> {
133 self.metadata.updated_by.as_ref()
134 }
135
136 pub fn deleted_by(&self) -> Option<&Uuid> {
138 self.metadata.deleted_by.as_ref()
139 }
140
141 pub fn status(&self) -> &PosClosingStatus {
143 &self.status
144 }
145
146
147 pub fn with_totals_by_method(mut self, value: serde_json::Value) -> Self {
153 self.totals_by_method = Some(value);
154 self
155 }
156
157 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
163 for (key, value) in fields {
164 match key.as_str() {
165 "pos_profile_id" => {
166 if let Ok(v) = serde_json::from_value(value) { self.pos_profile_id = v; }
167 }
168 "opening_entry_id" => {
169 if let Ok(v) = serde_json::from_value(value) { self.opening_entry_id = v; }
170 }
171 "closed_at" => {
172 if let Ok(v) = serde_json::from_value(value) { self.closed_at = v; }
173 }
174 "cashier_party_id" => {
175 if let Ok(v) = serde_json::from_value(value) { self.cashier_party_id = v; }
176 }
177 "totals_by_method" => {
178 if let Ok(v) = serde_json::from_value(value) { self.totals_by_method = v; }
179 }
180 "grand_total" => {
181 if let Ok(v) = serde_json::from_value(value) { self.grand_total = v; }
182 }
183 "invoice_count" => {
184 if let Ok(v) = serde_json::from_value(value) { self.invoice_count = v; }
185 }
186 "difference_total" => {
187 if let Ok(v) = serde_json::from_value(value) { self.difference_total = v; }
188 }
189 "status" => {
190 if let Ok(v) = serde_json::from_value(value) { self.status = v; }
191 }
192 _ => {} }
194 }
195 }
196
197}
198
199impl super::Entity for PosClosingEntry {
200 type Id = Uuid;
201
202 fn entity_id(&self) -> &Self::Id {
203 &self.id
204 }
205
206 fn entity_type() -> &'static str {
207 "PosClosingEntry"
208 }
209}
210
211impl backbone_core::PersistentEntity for PosClosingEntry {
212 fn entity_id(&self) -> String {
213 self.id.to_string()
214 }
215 fn set_entity_id(&mut self, id: String) {
216 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
217 self.id = uuid;
218 }
219 }
220 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
221 self.metadata.created_at
222 }
223 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
224 self.metadata.created_at = Some(ts);
225 }
226 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
227 self.metadata.updated_at
228 }
229 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
230 self.metadata.updated_at = Some(ts);
231 }
232 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
233 self.metadata.deleted_at
234 }
235 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
236 self.metadata.deleted_at = ts;
237 }
238}
239
240impl backbone_orm::EntityRepoMeta for PosClosingEntry {
241 fn column_types() -> std::collections::HashMap<String, String> {
242 let mut m = std::collections::HashMap::new();
243 m.insert("id".to_string(), "uuid".to_string());
244 m.insert("pos_profile_id".to_string(), "uuid".to_string());
245 m.insert("opening_entry_id".to_string(), "uuid".to_string());
246 m.insert("cashier_party_id".to_string(), "uuid".to_string());
247 m.insert("status".to_string(), "pos_closing_status".to_string());
248 m.insert("closed_at".to_string(), "timestamptz".to_string());
249 m
250 }
251 fn search_fields() -> &'static [&'static str] {
252 &[]
253 }
254}
255
256#[derive(Debug, Clone, Default)]
261pub struct PosClosingEntryBuilder {
262 pos_profile_id: Option<Uuid>,
263 opening_entry_id: Option<Uuid>,
264 closed_at: Option<DateTime<Utc>>,
265 cashier_party_id: Option<Uuid>,
266 totals_by_method: Option<serde_json::Value>,
267 grand_total: Option<Decimal>,
268 invoice_count: Option<i32>,
269 difference_total: Option<Decimal>,
270 status: Option<PosClosingStatus>,
271}
272
273impl PosClosingEntryBuilder {
274 pub fn pos_profile_id(mut self, value: Uuid) -> Self {
276 self.pos_profile_id = Some(value);
277 self
278 }
279
280 pub fn opening_entry_id(mut self, value: Uuid) -> Self {
282 self.opening_entry_id = Some(value);
283 self
284 }
285
286 pub fn closed_at(mut self, value: DateTime<Utc>) -> Self {
288 self.closed_at = Some(value);
289 self
290 }
291
292 pub fn cashier_party_id(mut self, value: Uuid) -> Self {
294 self.cashier_party_id = Some(value);
295 self
296 }
297
298 pub fn totals_by_method(mut self, value: serde_json::Value) -> Self {
300 self.totals_by_method = Some(value);
301 self
302 }
303
304 pub fn grand_total(mut self, value: Decimal) -> Self {
306 self.grand_total = Some(value);
307 self
308 }
309
310 pub fn invoice_count(mut self, value: i32) -> Self {
312 self.invoice_count = Some(value);
313 self
314 }
315
316 pub fn difference_total(mut self, value: Decimal) -> Self {
318 self.difference_total = Some(value);
319 self
320 }
321
322 pub fn status(mut self, value: PosClosingStatus) -> Self {
324 self.status = Some(value);
325 self
326 }
327
328 pub fn build(self) -> Result<PosClosingEntry, String> {
332 let pos_profile_id = self.pos_profile_id.ok_or_else(|| "pos_profile_id is required".to_string())?;
333 let opening_entry_id = self.opening_entry_id.ok_or_else(|| "opening_entry_id is required".to_string())?;
334 let closed_at = self.closed_at.ok_or_else(|| "closed_at is required".to_string())?;
335 let cashier_party_id = self.cashier_party_id.ok_or_else(|| "cashier_party_id is required".to_string())?;
336
337 Ok(PosClosingEntry {
338 id: Uuid::new_v4(),
339 pos_profile_id,
340 opening_entry_id,
341 closed_at,
342 cashier_party_id,
343 totals_by_method: self.totals_by_method,
344 grand_total: self.grand_total.unwrap_or(Decimal::from(0)),
345 invoice_count: self.invoice_count.unwrap_or(0),
346 difference_total: self.difference_total.unwrap_or(Decimal::from(0)),
347 status: self.status.unwrap_or_default(),
348 metadata: AuditMetadata::default(),
349 })
350 }
351}