1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::PosInvoiceStatus;
8use super::AuditMetadata;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct PosInvoiceId(pub Uuid);
14
15impl PosInvoiceId {
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 PosInvoiceId {
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 PosInvoiceId {
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 PosInvoiceId {
35 fn from(id: Uuid) -> Self { Self(id) }
36}
37
38impl From<PosInvoiceId> for Uuid {
39 fn from(id: PosInvoiceId) -> Self { id.0 }
40}
41
42impl AsRef<Uuid> for PosInvoiceId {
43 fn as_ref(&self) -> &Uuid { &self.0 }
44}
45
46impl std::ops::Deref for PosInvoiceId {
47 type Target = Uuid;
48 fn deref(&self) -> &Self::Target { &self.0 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
52pub struct PosInvoice {
53 pub id: Uuid,
54 pub pos_profile_id: Uuid,
55 pub opening_entry_id: Uuid,
56 pub branch_id: Option<Uuid>,
57 pub customer_id: Option<Uuid>,
58 pub pos_table_id: Option<Uuid>,
59 pub receipt_number: String,
60 pub client_uuid: Option<Uuid>,
61 pub posting_at: DateTime<Utc>,
62 pub net_total: Decimal,
63 pub tax_total: Decimal,
64 pub grand_total: Decimal,
65 pub rounding_adjustment: Decimal,
66 pub rounded_total: Decimal,
67 pub paid_total: Decimal,
68 pub change_due: Decimal,
69 pub billing_invoice_id: Option<Uuid>,
70 pub payment_entry_id: Option<Uuid>,
71 pub stock_delivery_note_id: Option<Uuid>,
72 pub is_return: bool,
73 pub return_against: Option<Uuid>,
74 pub status: PosInvoiceStatus,
75 #[serde(default)]
76 #[sqlx(json)]
77 pub metadata: AuditMetadata,
78}
79
80impl PosInvoice {
81 pub fn builder() -> PosInvoiceBuilder {
83 <PosInvoiceBuilder as Default>::default()
84 }
85
86 pub fn new(pos_profile_id: Uuid, opening_entry_id: Uuid, receipt_number: String, posting_at: DateTime<Utc>, net_total: Decimal, tax_total: Decimal, grand_total: Decimal, rounding_adjustment: Decimal, rounded_total: Decimal, paid_total: Decimal, change_due: Decimal, is_return: bool, status: PosInvoiceStatus) -> Self {
88 Self {
89 id: Uuid::new_v4(),
90 pos_profile_id,
91 opening_entry_id,
92 branch_id: None,
93 customer_id: None,
94 pos_table_id: None,
95 receipt_number,
96 client_uuid: None,
97 posting_at,
98 net_total,
99 tax_total,
100 grand_total,
101 rounding_adjustment,
102 rounded_total,
103 paid_total,
104 change_due,
105 billing_invoice_id: None,
106 payment_entry_id: None,
107 stock_delivery_note_id: None,
108 is_return,
109 return_against: None,
110 status,
111 metadata: AuditMetadata::default(),
112 }
113 }
114
115 pub fn id(&self) -> &Uuid {
117 &self.id
118 }
119
120 pub fn typed_id(&self) -> PosInvoiceId {
122 PosInvoiceId(self.id)
123 }
124
125 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
127 self.metadata.created_at.as_ref()
128 }
129
130 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
132 self.metadata.updated_at.as_ref()
133 }
134
135 pub fn is_deleted(&self) -> bool {
137 self.metadata.deleted_at.is_some()
138 }
139
140 pub fn is_active(&self) -> bool {
142 self.metadata.deleted_at.is_none()
143 }
144
145 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
147 self.metadata.deleted_at.as_ref()
148 }
149
150 pub fn created_by(&self) -> Option<&Uuid> {
152 self.metadata.created_by.as_ref()
153 }
154
155 pub fn updated_by(&self) -> Option<&Uuid> {
157 self.metadata.updated_by.as_ref()
158 }
159
160 pub fn deleted_by(&self) -> Option<&Uuid> {
162 self.metadata.deleted_by.as_ref()
163 }
164
165 pub fn status(&self) -> &PosInvoiceStatus {
167 &self.status
168 }
169
170
171 pub fn with_branch_id(mut self, value: Uuid) -> Self {
177 self.branch_id = Some(value);
178 self
179 }
180
181 pub fn with_customer_id(mut self, value: Uuid) -> Self {
183 self.customer_id = Some(value);
184 self
185 }
186
187 pub fn with_pos_table_id(mut self, value: Uuid) -> Self {
189 self.pos_table_id = Some(value);
190 self
191 }
192
193 pub fn with_client_uuid(mut self, value: Uuid) -> Self {
195 self.client_uuid = Some(value);
196 self
197 }
198
199 pub fn with_billing_invoice_id(mut self, value: Uuid) -> Self {
201 self.billing_invoice_id = Some(value);
202 self
203 }
204
205 pub fn with_payment_entry_id(mut self, value: Uuid) -> Self {
207 self.payment_entry_id = Some(value);
208 self
209 }
210
211 pub fn with_stock_delivery_note_id(mut self, value: Uuid) -> Self {
213 self.stock_delivery_note_id = Some(value);
214 self
215 }
216
217 pub fn with_return_against(mut self, value: Uuid) -> Self {
219 self.return_against = Some(value);
220 self
221 }
222
223 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
229 for (key, value) in fields {
230 match key.as_str() {
231 "pos_profile_id" => {
232 if let Ok(v) = serde_json::from_value(value) { self.pos_profile_id = v; }
233 }
234 "opening_entry_id" => {
235 if let Ok(v) = serde_json::from_value(value) { self.opening_entry_id = v; }
236 }
237 "branch_id" => {
238 if let Ok(v) = serde_json::from_value(value) { self.branch_id = v; }
239 }
240 "customer_id" => {
241 if let Ok(v) = serde_json::from_value(value) { self.customer_id = v; }
242 }
243 "pos_table_id" => {
244 if let Ok(v) = serde_json::from_value(value) { self.pos_table_id = v; }
245 }
246 "receipt_number" => {
247 if let Ok(v) = serde_json::from_value(value) { self.receipt_number = v; }
248 }
249 "client_uuid" => {
250 if let Ok(v) = serde_json::from_value(value) { self.client_uuid = v; }
251 }
252 "posting_at" => {
253 if let Ok(v) = serde_json::from_value(value) { self.posting_at = v; }
254 }
255 "net_total" => {
256 if let Ok(v) = serde_json::from_value(value) { self.net_total = v; }
257 }
258 "tax_total" => {
259 if let Ok(v) = serde_json::from_value(value) { self.tax_total = v; }
260 }
261 "grand_total" => {
262 if let Ok(v) = serde_json::from_value(value) { self.grand_total = v; }
263 }
264 "rounding_adjustment" => {
265 if let Ok(v) = serde_json::from_value(value) { self.rounding_adjustment = v; }
266 }
267 "rounded_total" => {
268 if let Ok(v) = serde_json::from_value(value) { self.rounded_total = v; }
269 }
270 "paid_total" => {
271 if let Ok(v) = serde_json::from_value(value) { self.paid_total = v; }
272 }
273 "change_due" => {
274 if let Ok(v) = serde_json::from_value(value) { self.change_due = v; }
275 }
276 "billing_invoice_id" => {
277 if let Ok(v) = serde_json::from_value(value) { self.billing_invoice_id = v; }
278 }
279 "payment_entry_id" => {
280 if let Ok(v) = serde_json::from_value(value) { self.payment_entry_id = v; }
281 }
282 "stock_delivery_note_id" => {
283 if let Ok(v) = serde_json::from_value(value) { self.stock_delivery_note_id = v; }
284 }
285 "is_return" => {
286 if let Ok(v) = serde_json::from_value(value) { self.is_return = v; }
287 }
288 "return_against" => {
289 if let Ok(v) = serde_json::from_value(value) { self.return_against = v; }
290 }
291 "status" => {
292 if let Ok(v) = serde_json::from_value(value) { self.status = v; }
293 }
294 _ => {} }
296 }
297 }
298
299}
300
301impl super::Entity for PosInvoice {
302 type Id = Uuid;
303
304 fn entity_id(&self) -> &Self::Id {
305 &self.id
306 }
307
308 fn entity_type() -> &'static str {
309 "PosInvoice"
310 }
311}
312
313impl backbone_core::PersistentEntity for PosInvoice {
314 fn write_protected_fields() -> &'static [&'static str] {
315 &["client_uuid", "net_total", "tax_total", "grand_total", "rounding_adjustment", "rounded_total", "paid_total", "change_due", "billing_invoice_id", "payment_entry_id", "stock_delivery_note_id", "is_return", "return_against", "status"]
316 }
317 fn entity_id(&self) -> String {
318 self.id.to_string()
319 }
320 fn set_entity_id(&mut self, id: String) {
321 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
322 self.id = uuid;
323 }
324 }
325 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
326 self.metadata.created_at
327 }
328 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
329 self.metadata.created_at = Some(ts);
330 }
331 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
332 self.metadata.updated_at
333 }
334 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
335 self.metadata.updated_at = Some(ts);
336 }
337 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
338 self.metadata.deleted_at
339 }
340 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
341 self.metadata.deleted_at = ts;
342 }
343}
344
345impl backbone_orm::EntityRepoMeta for PosInvoice {
346 fn column_types() -> std::collections::HashMap<String, String> {
347 let mut m = std::collections::HashMap::new();
348 m.insert("id".to_string(), "uuid".to_string());
349 m.insert("pos_profile_id".to_string(), "uuid".to_string());
350 m.insert("opening_entry_id".to_string(), "uuid".to_string());
351 m.insert("branch_id".to_string(), "uuid".to_string());
352 m.insert("customer_id".to_string(), "uuid".to_string());
353 m.insert("pos_table_id".to_string(), "uuid".to_string());
354 m.insert("billing_invoice_id".to_string(), "uuid".to_string());
355 m.insert("payment_entry_id".to_string(), "uuid".to_string());
356 m.insert("stock_delivery_note_id".to_string(), "uuid".to_string());
357 m.insert("status".to_string(), "pos_invoice_status".to_string());
358 m.insert("posting_at".to_string(), "timestamptz".to_string());
359 m
360 }
361 fn search_fields() -> &'static [&'static str] {
362 &["receipt_number"]
363 }
364}
365
366#[derive(Debug, Clone, Default)]
371pub struct PosInvoiceBuilder {
372 pos_profile_id: Option<Uuid>,
373 opening_entry_id: Option<Uuid>,
374 branch_id: Option<Uuid>,
375 customer_id: Option<Uuid>,
376 pos_table_id: Option<Uuid>,
377 receipt_number: Option<String>,
378 client_uuid: Option<Uuid>,
379 posting_at: Option<DateTime<Utc>>,
380 net_total: Option<Decimal>,
381 tax_total: Option<Decimal>,
382 grand_total: Option<Decimal>,
383 rounding_adjustment: Option<Decimal>,
384 rounded_total: Option<Decimal>,
385 paid_total: Option<Decimal>,
386 change_due: Option<Decimal>,
387 billing_invoice_id: Option<Uuid>,
388 payment_entry_id: Option<Uuid>,
389 stock_delivery_note_id: Option<Uuid>,
390 is_return: Option<bool>,
391 return_against: Option<Uuid>,
392 status: Option<PosInvoiceStatus>,
393}
394
395impl PosInvoiceBuilder {
396 pub fn pos_profile_id(mut self, value: Uuid) -> Self {
398 self.pos_profile_id = Some(value);
399 self
400 }
401
402 pub fn opening_entry_id(mut self, value: Uuid) -> Self {
404 self.opening_entry_id = Some(value);
405 self
406 }
407
408 pub fn branch_id(mut self, value: Uuid) -> Self {
410 self.branch_id = Some(value);
411 self
412 }
413
414 pub fn customer_id(mut self, value: Uuid) -> Self {
416 self.customer_id = Some(value);
417 self
418 }
419
420 pub fn pos_table_id(mut self, value: Uuid) -> Self {
422 self.pos_table_id = Some(value);
423 self
424 }
425
426 pub fn receipt_number(mut self, value: String) -> Self {
428 self.receipt_number = Some(value);
429 self
430 }
431
432 pub fn client_uuid(mut self, value: Uuid) -> Self {
434 self.client_uuid = Some(value);
435 self
436 }
437
438 pub fn posting_at(mut self, value: DateTime<Utc>) -> Self {
440 self.posting_at = Some(value);
441 self
442 }
443
444 pub fn net_total(mut self, value: Decimal) -> Self {
446 self.net_total = Some(value);
447 self
448 }
449
450 pub fn tax_total(mut self, value: Decimal) -> Self {
452 self.tax_total = Some(value);
453 self
454 }
455
456 pub fn grand_total(mut self, value: Decimal) -> Self {
458 self.grand_total = Some(value);
459 self
460 }
461
462 pub fn rounding_adjustment(mut self, value: Decimal) -> Self {
464 self.rounding_adjustment = Some(value);
465 self
466 }
467
468 pub fn rounded_total(mut self, value: Decimal) -> Self {
470 self.rounded_total = Some(value);
471 self
472 }
473
474 pub fn paid_total(mut self, value: Decimal) -> Self {
476 self.paid_total = Some(value);
477 self
478 }
479
480 pub fn change_due(mut self, value: Decimal) -> Self {
482 self.change_due = Some(value);
483 self
484 }
485
486 pub fn billing_invoice_id(mut self, value: Uuid) -> Self {
488 self.billing_invoice_id = Some(value);
489 self
490 }
491
492 pub fn payment_entry_id(mut self, value: Uuid) -> Self {
494 self.payment_entry_id = Some(value);
495 self
496 }
497
498 pub fn stock_delivery_note_id(mut self, value: Uuid) -> Self {
500 self.stock_delivery_note_id = Some(value);
501 self
502 }
503
504 pub fn is_return(mut self, value: bool) -> Self {
506 self.is_return = Some(value);
507 self
508 }
509
510 pub fn return_against(mut self, value: Uuid) -> Self {
512 self.return_against = Some(value);
513 self
514 }
515
516 pub fn status(mut self, value: PosInvoiceStatus) -> Self {
518 self.status = Some(value);
519 self
520 }
521
522 pub fn build(self) -> Result<PosInvoice, String> {
526 let pos_profile_id = self.pos_profile_id.ok_or_else(|| "pos_profile_id is required".to_string())?;
527 let opening_entry_id = self.opening_entry_id.ok_or_else(|| "opening_entry_id is required".to_string())?;
528 let receipt_number = self.receipt_number.ok_or_else(|| "receipt_number is required".to_string())?;
529 let posting_at = self.posting_at.ok_or_else(|| "posting_at is required".to_string())?;
530
531 Ok(PosInvoice {
532 id: Uuid::new_v4(),
533 pos_profile_id,
534 opening_entry_id,
535 branch_id: self.branch_id,
536 customer_id: self.customer_id,
537 pos_table_id: self.pos_table_id,
538 receipt_number,
539 client_uuid: self.client_uuid,
540 posting_at,
541 net_total: self.net_total.unwrap_or(Decimal::from(0)),
542 tax_total: self.tax_total.unwrap_or(Decimal::from(0)),
543 grand_total: self.grand_total.unwrap_or(Decimal::from(0)),
544 rounding_adjustment: self.rounding_adjustment.unwrap_or(Decimal::from(0)),
545 rounded_total: self.rounded_total.unwrap_or(Decimal::from(0)),
546 paid_total: self.paid_total.unwrap_or(Decimal::from(0)),
547 change_due: self.change_due.unwrap_or(Decimal::from(0)),
548 billing_invoice_id: self.billing_invoice_id,
549 payment_entry_id: self.payment_entry_id,
550 stock_delivery_note_id: self.stock_delivery_note_id,
551 is_return: self.is_return.unwrap_or(false),
552 return_against: self.return_against,
553 status: self.status.unwrap_or_default(),
554 metadata: AuditMetadata::default(),
555 })
556 }
557}