1use bigdecimal::{BigDecimal, FromPrimitive, Zero};
16use discount::Discounter;
17use serde::Serialize;
18use std::{fmt, str::FromStr};
19use tax::Taxer;
20
21pub mod discount;
22pub mod tax;
23
24pub fn hundred() -> BigDecimal {
26 BigDecimal::from_str("100.0").unwrap()
27}
28
29pub fn one() -> BigDecimal {
31 BigDecimal::from_str("1.0").unwrap()
32}
33
34pub fn inverse() -> BigDecimal {
36 BigDecimal::from_str("-1.0").unwrap()
37}
38
39pub fn zero() -> BigDecimal {
41 BigDecimal::zero()
42}
43
44#[derive(Debug)]
45pub enum BagginsError<S: Into<String>> {
77
78 NegativeQty(S),
80
81 InvalidDecimalValue(S),
83
84 Other(S),
86}
87
88impl<S: Into<String> + Clone> fmt::Display for BagginsError<S> {
89 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90 match self {
91 BagginsError::NegativeQty(msg) => {
92 write!(f, "Negative quantity value Error: {}", msg.clone().into())
93 }
94 BagginsError::InvalidDecimalValue(msg) => {
95 write!(f, " Invalid decimal value {}", msg.clone().into())
96 }
97 BagginsError::Other(msg) => write!(f, " Error {}", msg.clone().into()),
98 }
99 }
100}
101
102#[derive(Debug, Serialize)]
103pub struct CalculationWithDiscount {
105 pub net: BigDecimal,
107 pub brute: BigDecimal,
109 pub tax: BigDecimal,
111 pub discount_value: BigDecimal,
113 pub discount_brute_value: BigDecimal,
115 pub total_discount_percent: BigDecimal,
117 pub unit_value: BigDecimal,
119}
120
121impl CalculationWithDiscount {
122 pub fn new(
124 net: BigDecimal,
125 brute: BigDecimal,
126 tax: BigDecimal,
127 discount_value: BigDecimal,
128 discount_brute_value: BigDecimal,
129 total_discount_percent: BigDecimal,
130 unit_value: BigDecimal,
131 ) -> Self {
132 Self {
133 net,
134 brute,
135 tax,
136 discount_value,
137 discount_brute_value,
138 total_discount_percent,
139 unit_value,
140 }
141 }
142
143 pub fn round(&self, scale: i64) -> Self {
144 let scale = if !(0..=128).contains(&scale) { 128 } else { scale };
145 Self {
146 net: self.net.round(scale),
147 brute: self.brute.round(scale),
148 tax: self.tax.round(scale),
149 discount_value: self.discount_value.round(scale),
150 discount_brute_value: self.discount_brute_value.round(scale),
151 total_discount_percent: self.total_discount_percent.round(scale),
152 unit_value: self.unit_value.clone(),
153 }
154 }
155}
156
157impl Default for CalculationWithDiscount {
158 fn default() -> Self {
159 Self {
160 net: zero(),
161 brute: zero(),
162 tax: zero(),
163 discount_value: zero(),
164 discount_brute_value: zero(),
165 total_discount_percent: zero(),
166 unit_value: zero(),
167 }
168 }
169}
170
171impl fmt::Display for CalculationWithDiscount {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 write!(f, "net {}, brute {}, tax {}, discount value {}, discount brute value {}, total discount percent {}, unit_value {} )",
174 self.net,
175 self.brute,
176 self.tax,
177 self.discount_value,
178 self.discount_brute_value,
179 self.total_discount_percent,
180 self.unit_value,
181 )
182 }
183}
184
185#[derive(Debug, Serialize)]
186pub struct CalculationWithoutDiscount {
188 pub net: BigDecimal,
190 pub brute: BigDecimal,
192 pub tax: BigDecimal,
194 pub unit_value: BigDecimal,
196}
197
198impl CalculationWithoutDiscount {
199 pub fn new(
201 net: BigDecimal,
202 brute: BigDecimal,
203 tax: BigDecimal,
204 unit_value: BigDecimal,
205 ) -> Self {
206 Self {
207 net,
208 brute,
209 tax,
210 unit_value,
211 }
212 }
213
214 pub fn round(&self, scale: i64) -> Self {
215 let scale = if !(0..=128).contains(&scale) { 128 } else { scale };
216 Self {
217 net: self.net.round(scale),
218 brute: self.brute.round(scale),
219 tax: self.tax.round(scale),
220 unit_value: self.unit_value.clone(),
221 }
222 }
223}
224
225impl Default for CalculationWithoutDiscount {
226 fn default() -> Self {
227 Self {
228 net: zero(),
229 brute: zero(),
230 tax: zero(),
231 unit_value: zero(),
232 }
233 }
234}
235
236impl fmt::Display for CalculationWithoutDiscount {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 write!(
239 f,
240 "net {}, brute {}, tax {}, unit_value {})",
241 self.net, self.brute, self.tax, self.unit_value,
242 )
243 }
244}
245
246#[derive(Debug, Serialize, Default)]
247pub struct Calculation {
248 without_discount_values: CalculationWithoutDiscount,
249 with_discount_values: CalculationWithDiscount,
250}
251
252
253impl Calculation {
254 pub fn new(
255 without_discount_values: CalculationWithoutDiscount,
256 with_discount_values: CalculationWithDiscount,
257 ) -> Self {
258 Self {
259 without_discount_values,
260 with_discount_values,
261 }
262 }
263}
264
265impl fmt::Display for Calculation {
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 write!(
268 f,
269 "without_discount_valueset {}, with_discount_values {}",
270 self.without_discount_values, self.with_discount_values,
271 )
272 }
273}
274
275pub trait Calculator {
277 fn add_discount(
279 &mut self,
280 discount: BigDecimal,
281 discount_mode: discount::Mode,
282 ) -> Option<discount::DiscountError<String>>;
283
284 fn add_discount_from_f64(
287 &mut self,
288 discount: f64,
289 discount_mode: discount::Mode,
290 ) -> Option<discount::DiscountError<String>>;
291
292 fn add_discount_from_str<S: Into<String>>(
295 &mut self,
296 discount: S,
297 discount_mode: discount::Mode,
298 ) -> Option<discount::DiscountError<String>>;
299
300 fn add_tax_from_f64(
303 &mut self,
304 tax: f64,
305 stage: tax::Stage,
306 tax_mode: tax::Mode,
307 ) -> Option<tax::TaxError<String>>;
308
309 fn add_tax(
311 &mut self,
312 tax: BigDecimal,
313 stage: tax::Stage,
314 tax_mode: tax::Mode,
315 ) -> Option<tax::TaxError<String>>;
316
317 fn add_tax_from_str<S: Into<String>>(
319 &mut self,
320 tax: S,
321 stage: tax::Stage,
322 tax_mode: tax::Mode,
323 ) -> Option<tax::TaxError<String>>;
324
325 fn compute_from_brute(
328 &mut self,
329 brute: BigDecimal,
330 qty: BigDecimal,
331 max_discount_allowed: Option<BigDecimal>,
332 ) -> Result<Calculation, BagginsError<String>>;
333
334 fn compute_from_brute_f64(
337 &mut self,
338 brute: f64,
339 qty: f64,
340 max_discount_allowed: Option<f64>,
341 ) -> Result<Calculation, BagginsError<String>>;
342
343 fn compute_from_brute_str<S: Into<String>>(
346 &mut self,
347 brute: S,
348 qty: S,
349 max_discount_allowed: Option<S>,
350 ) -> Result<Calculation, BagginsError<String>>;
351
352 fn compute_from_str<S: Into<String>>(
355 &mut self,
356 unit_value: S,
357 qty: S,
358 max_discount_allowed: Option<S>,
359 ) -> Result<Calculation, BagginsError<String>>;
360
361 fn compute_from_f64(
364 &mut self,
365 unit_value: f64,
366 qty: f64,
367 max_discount_allowed: Option<f64>,
368 ) -> Result<Calculation, BagginsError<String>>;
369
370 fn compute(
377 &mut self,
378 unit_value: BigDecimal,
379 qty: BigDecimal,
380 max_discount_allowed: Option<BigDecimal>,
381 ) -> Result<Calculation, BagginsError<String>>;
382
383 fn line_tax(
396 &mut self,
397 taxable: BigDecimal,
398 qty: BigDecimal,
399 value: BigDecimal,
400 mode: tax::Mode,
401 ) -> Result<BigDecimal, tax::TaxError<String>>;
402
403 fn line_tax_from_str<S: Into<String>>(
417 &mut self,
418 taxable: S,
419 qty: S,
420 value: S,
421 mode: tax::Mode,
422 ) -> Result<BigDecimal, tax::TaxError<String>>;
423
424 fn line_tax_from_f64(
438 &mut self,
439 taxable: f64,
440 qty: f64,
441 value: f64,
442 mode: tax::Mode,
443 ) -> Result<BigDecimal, tax::TaxError<String>>;
444}
445
446pub struct DetailCalculator {
447 tax_handler: tax::TaxComputer,
448 discount_handler: discount::DiscountComputer,
449}
450
451impl DetailCalculator {
452 pub fn new() -> Self {
454 Self {
455 tax_handler: tax::TaxComputer::default(),
456 discount_handler: discount::DiscountComputer::default(),
457 }
458 }
459}
460
461impl Default for DetailCalculator {
462 fn default() -> Self {
463 Self::new()
464 }
465}
466
467impl Calculator for DetailCalculator {
468 fn add_discount(
469 &mut self,
470 discount: BigDecimal,
471 discount_mode: discount::Mode,
472 ) -> Option<discount::DiscountError<String>> {
473 self.discount_handler.add_discount(discount, discount_mode)
474 }
475
476 fn add_discount_from_f64(
477 &mut self,
478 discount: f64,
479 discount_mode: discount::Mode,
480 ) -> Option<discount::DiscountError<String>> {
481 self.discount_handler
482 .add_discount_from_f64(discount, discount_mode)
483 }
484
485 fn add_discount_from_str<S: Into<String>>(
486 &mut self,
487 discount: S,
488 discount_mode: discount::Mode,
489 ) -> Option<discount::DiscountError<String>> {
490 self.discount_handler
491 .add_discount_from_str(discount, discount_mode)
492 }
493
494 fn add_tax_from_f64(
495 &mut self,
496 tax: f64,
497 stage: tax::Stage,
498 tax_mode: tax::Mode,
499 ) -> Option<tax::TaxError<String>> {
500 self.tax_handler.add_tax_from_f64(tax, stage, tax_mode)
501 }
502
503 fn add_tax(
504 &mut self,
505 tax: BigDecimal,
506 stage: tax::Stage,
507 tax_mode: tax::Mode,
508 ) -> Option<tax::TaxError<String>> {
509 self.tax_handler.add_tax(tax, stage, tax_mode)
510 }
511
512 fn add_tax_from_str<S: Into<String>>(
513 &mut self,
514 tax: S,
515 stage: tax::Stage,
516 tax_mode: tax::Mode,
517 ) -> Option<tax::TaxError<String>> {
518 self.tax_handler.add_tax_from_str(tax, stage, tax_mode)
519 }
520
521 fn compute_from_brute(
522 &mut self,
523 brute: BigDecimal,
524 qty: BigDecimal,
525 max_discount_allowed: Option<BigDecimal>,
526 ) -> Result<Calculation, BagginsError<String>> {
527 match self.tax_handler.un_tax(brute.clone(), qty.clone()) {
528 Ok(un_taxed) => match self
529 .discount_handler
530 .un_discount(un_taxed.clone(), qty.clone())
531 {
532 Ok(un_discounted) => self.compute(un_discounted.0, qty, max_discount_allowed),
533 Err(err) => Err(BagginsError::Other(format!(
534 "undiscounting un_taxed {} {}",
535 un_taxed, err
536 ))),
537 },
538 Err(err) => Err(BagginsError::Other(format!(
539 "untaxing brute {} {}",
540 brute, err
541 ))),
542 }
543 }
544
545 fn compute_from_brute_f64(
546 &mut self,
547 brute: f64,
548 qty: f64,
549 max_discount_allowed: Option<f64>,
550 ) -> Result<Calculation, BagginsError<String>> {
551
552 let max_discount_allowed: Option<BigDecimal> = BigDecimal::from_f64(max_discount_allowed.unwrap_or(100.0f64));
553
554 self.compute_from_brute(
555 BigDecimal::from_f64(brute).unwrap_or(inverse()),
556 BigDecimal::from_f64(qty).unwrap_or(inverse()),
557 max_discount_allowed,
558 )
559 }
560
561 fn compute_from_brute_str<S: Into<String>>(
562 &mut self,
563 brute: S,
564 qty: S,
565 max_discount_allowed: Option<S>,
566 ) -> Result<Calculation, BagginsError<String>> {
567 let brute = brute.into();
568 let qty = qty.into();
569
570 match BigDecimal::from_str(&brute) {
571 Ok(brute) => match BigDecimal::from_str(&qty) {
572 Ok(qty) => match max_discount_allowed {
573 Some(max_discount_allowed) => {
574 let max_discount_allowed = max_discount_allowed.into();
575
576 match BigDecimal::from_str(&max_discount_allowed) {
577 Ok(max_discount_allowed) => {
578 self.compute(brute, qty, Some(max_discount_allowed))
579 }
580 Err(err) => Err(BagginsError::InvalidDecimalValue(format!(
581 "parsing max_discount_allowed: <S: Into<String>> {} {}",
582 max_discount_allowed, err,
583 ))),
584 }
585 }
586 None => self.compute(brute, qty, None),
587 },
588 Err(err) => Err(BagginsError::InvalidDecimalValue(format!(
589 "parsing qty: <S: Into<String>> {} {}",
590 qty, err,
591 ))),
592 },
593 Err(err) => Err(BagginsError::InvalidDecimalValue(format!(
594 "parsing unit_value: <S: Into<String>> {} {}",
595 brute, err,
596 ))),
597 }
598 }
599
600 fn compute_from_str<S: Into<String>>(
601 &mut self,
602 unit_value: S,
603 qty: S,
604 max_discount_allowed: Option<S>,
605 ) -> Result<Calculation, BagginsError<String>> {
606 let unit_value = unit_value.into();
607 let qty = qty.into();
608
609 match BigDecimal::from_str(&unit_value) {
610 Ok(unit_value) => match BigDecimal::from_str(&qty) {
611 Ok(qty) => match max_discount_allowed {
612 Some(max_discount_allowed) => {
613 let max_discount_allowed = max_discount_allowed.into();
614
615 match BigDecimal::from_str(&max_discount_allowed) {
616 Ok(max_discount_allowed) => {
617 self.compute(unit_value, qty, Some(max_discount_allowed))
618 }
619 Err(err) => Err(BagginsError::InvalidDecimalValue(format!(
620 "parsing max_discount_allowed: <S: Into<String>> {} {}",
621 max_discount_allowed, err,
622 ))),
623 }
624 }
625 None => self.compute(unit_value, qty, None),
626 },
627 Err(err) => Err(BagginsError::InvalidDecimalValue(format!(
628 "parsing qty: <S: Into<String>> {} {}",
629 qty, err,
630 ))),
631 },
632 Err(err) => Err(BagginsError::InvalidDecimalValue(format!(
633 "parsing unit_value: <S: Into<String>> {} {}",
634 unit_value, err,
635 ))),
636 }
637 }
638
639 fn compute_from_f64(
640 &mut self,
641 unit_value: f64,
642 qty: f64,
643 max_discount_allowed: Option<f64>,
644 ) -> Result<Calculation, BagginsError<String>> {
645 let max_discount_allowed: Option<BigDecimal> = BigDecimal::from_f64(max_discount_allowed.unwrap_or(100.0f64));
646
647 self.compute(
648 BigDecimal::from_f64(unit_value).unwrap_or(inverse()),
649 BigDecimal::from_f64(qty).unwrap_or(inverse()),
650 max_discount_allowed,
651 )
652 }
653
654 fn compute(
655 &mut self,
656 unit_value: BigDecimal,
657 qty: BigDecimal,
658 max_discount_allowed: Option<BigDecimal>,
659 ) -> Result<Calculation, BagginsError<String>> {
660 match self
661 .discount_handler
662 .compute(unit_value.clone(), qty.clone(), max_discount_allowed)
663 {
664 Ok(discount) => {
665 let net = &unit_value * &qty - &discount.0;
666 let discounted_uv = &net / &qty;
667
668 match self.tax_handler.tax(discounted_uv.clone(), qty.clone()) {
669 Ok(tax) => match self.tax_handler.tax(unit_value.clone(), qty.clone()) {
670 Ok(tax_without_discount) => {
671 let net_without_discount = &unit_value * &qty;
672 let brute_without_discount =
673 &net_without_discount + &tax_without_discount;
674 let brute = &net + &tax;
675
676 let calc = Calculation {
677 without_discount_values: CalculationWithoutDiscount {
678 brute: brute_without_discount.clone(),
679 unit_value: &net_without_discount / &qty,
680 net: net_without_discount,
681 tax: tax_without_discount,
682 },
683 with_discount_values: CalculationWithDiscount {
684 discount_brute_value: &brute - &brute_without_discount,
685 brute,
686 unit_value: &net / &qty,
687 net,
688 tax,
689 discount_value: discount.0,
690 total_discount_percent: discount.1,
691 },
692 };
693
694 Ok(calc)
695 }
696 Err(err) => Err(BagginsError::Other(format!(
697 "calculating taxes {}",
698 err
699 ))),
700 },
701 Err(err) => Err(BagginsError::Other(format!(
702 "calculating taxes {}",
703 err
704 ))),
705 }
706 }
707 Err(err) => Err(BagginsError::Other(format!(
708 "calculating discount {}",
709 err
710 ))),
711 }
712 }
713
714 fn line_tax(
715 &mut self,
716 taxable: BigDecimal,
717 qty: BigDecimal,
718 value: BigDecimal,
719 mode: tax::Mode,
720 ) -> Result<BigDecimal, tax::TaxError<String>> {
721 self.tax_handler.line_tax(taxable, qty, value, mode)
722 }
723
724 fn line_tax_from_str<S: Into<String>>(
725 &mut self,
726 taxable: S,
727 qty: S,
728 value: S,
729 mode: tax::Mode,
730 ) -> Result<BigDecimal, tax::TaxError<String>> {
731 self.tax_handler
732 .line_tax_from_str(taxable, qty, value, mode)
733 }
734
735 fn line_tax_from_f64(
736 &mut self,
737 taxable: f64,
738 qty: f64,
739 value: f64,
740 mode: tax::Mode,
741 ) -> Result<BigDecimal, tax::TaxError<String>> {
742 self.tax_handler
743 .line_tax_from_f64(taxable, qty, value, mode)
744 }
745}