baggins/
lib.rs

1// Copyright 2023 Andrés Reyes El Programador Pobre.
2//
3//! baggins
4//!
5//! `baggins` provides a series of utilities to easily and efficiently calculate sales operations.
6//! Due to the nature of monetary calculations, the Bigdecimal crate is used as a backend.
7//!
8//! The focus is on ease of use and learning Rust, so there are many opportunities for improvement.
9//!
10//! `baggins` provee una serie de utilidades para calcular facil y eficientemente totales
11//! y subtotales de lineas de detalle de procesos de venta.
12//! El foco está en la facilidad de uso y en aprender Rust, por lo que hay muchas oportunidades de mejora.
13//!
14//!
15use 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
24/// handy utility to get 100.0 as BigDecimal
25pub fn hundred() -> BigDecimal {
26    BigDecimal::from_str("100.0").unwrap()
27}
28
29/// handy utility to get 1.0 as BigDecimal
30pub fn one() -> BigDecimal {
31    BigDecimal::from_str("1.0").unwrap()
32}
33
34/// handy utility to get -1.0 as BigDecimal
35pub fn inverse() -> BigDecimal {
36    BigDecimal::from_str("-1.0").unwrap()
37}
38
39/// handy utility to get 0.0 as BigDecimal. Just a wrapper for zero()
40pub fn zero() -> BigDecimal {
41    BigDecimal::zero()
42}
43
44#[derive(Debug)]
45/// The error type for baggins operations - El tipo de error para operaciones de baggins
46///
47/// Everything in life can end in a huge mistake, and the operations that baggins performs
48/// They are not the exception. We have tried to prepare the most common errors.
49///
50/// Todo en la vida puede terminar en un enorme error, y las operaciones que baggins realiza
51/// no son la excepción. Hemos tratado de preparar los errores mas comunes.
52///
53/// # Example
54///
55/// ```
56///  use baggins::discount::Mode;
57///  use baggins::DetailCalculator;
58///  use crate::baggins::Calculator;
59///
60///  let mut c = DetailCalculator::new();
61///
62///  c.add_discount_from_str("22.74", Mode::Percentual);
63///
64///  let r = c.compute_from_str("120.34", "-10", None);
65///
66///  match r {
67///      Ok(calculation) => {
68///          println!("this branch will not be executed");
69///      }
70///      Err(err) => {
71///          println!("this will print a bagginsError::NegativeQty {}", err);
72///      }
73///  }
74/// ```
75///
76pub enum BagginsError<S: Into<String>> {
77
78    /// Error due to pass a negative quantity
79    NegativeQty(S),
80
81    /// Error for not being able to convert a value to [BigDecimal]
82    InvalidDecimalValue(S),
83
84    /// Any other unspecified error
85    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)]
103/// will contain the result of the computing of the specified subtotal
104pub struct CalculationWithDiscount {
105    /// stores the unit value multiplied by the quantity minus the discount
106    pub net: BigDecimal,
107    /// stores the net plus taxes
108    pub brute: BigDecimal,
109    /// stores the cumulated tax calculated over net
110    pub tax: BigDecimal,
111    /// stores the cumulated discount value
112    pub discount_value: BigDecimal,
113    /// stores the cumulated discount value
114    pub discount_brute_value: BigDecimal,
115    /// stores the total discount applied as a percentage
116    pub total_discount_percent: BigDecimal,
117    /// stores the unit value with discounts applied
118    pub unit_value: BigDecimal,
119}
120
121impl CalculationWithDiscount {
122    /// Creates a new [`CalculationWithDiscount`].
123    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)]
186/// will contain the result of the computing of the specified subtotal without discounts
187pub struct CalculationWithoutDiscount {
188    /// stores the unit value multiplied by the quantity
189    pub net: BigDecimal,
190    /// stores the net plus taxes
191    pub brute: BigDecimal,
192    /// stores the cumulated tax calculated over net
193    pub tax: BigDecimal,
194    /// stores the used unit value
195    pub unit_value: BigDecimal,
196}
197
198impl CalculationWithoutDiscount {
199    /// Creates a new [`CalculationWithoutDiscount`].
200    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
275// A thing able to calculate sales values
276pub trait Calculator {
277    /// adds a [BigDecimal] discount value of the specified [discount::Mode] to [Calculator].
278    fn add_discount(
279        &mut self,
280        discount: BigDecimal,
281        discount_mode: discount::Mode,
282    ) -> Option<discount::DiscountError<String>>;
283
284    /// adds a [f64] discount value of the specified [discount::Mode]
285    /// to [Calculator] so expect some precision loss
286    fn add_discount_from_f64(
287        &mut self,
288        discount: f64,
289        discount_mode: discount::Mode,
290    ) -> Option<discount::DiscountError<String>>;
291
292    /// adds an [`Into<String>`] discount value of the specified [discount::Mode]
293    /// to [`Calculator`]
294    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    /// adds a tax to the specified [tax::Stage] in [Calculator] from a [f64] value
301    /// of the specified [tax::Mode] so expect some precision loss
302    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    /// adds a tax to the specified [tax::Stage] in [Calculator] from a [BigDecimal]
310    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    /// adds a tax to the specified [tax::Stage] in [Calculator] from a [String]
318    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    /// calculates and produces a [Calculation] from a [BigDecimal] brute value
326    /// and a quantity of the same type
327    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    /// calculates and produces a [Calculation] from a [f64] brute subtotal value
335    /// and a quantity of the same type. Use of [f64] may cause precission loss
336    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    /// calculates and produces a [Calculation] from a [String] brute value
344    /// and a quantity of the same type
345    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    /// calculates and produces a [Calculation] from a [String] unit value
353    /// and a quantity of the same type
354    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    /// calculates and produces a [Calculation] from a [f64] unit value
362    /// and a quantity of the same type. Use of [f64] may cause precission loss
363    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    /// calculates and produces a [Calculation] from a [BigDecimal] unit value
371    /// and a quantity of the same type
372    /// Receives an [Option<BigDecimal>] to use as a maximum discount value. If [None] the max discount value will be the
373    /// equivalent to the 100% of the calculated brute subtotal. If [Some] will be validated and used the passed value.
374    /// If the unwrapped [BigDecimal] is negative or greater than 100, a Bigdecimal with the 100 value will be used
375    /// as maximum allowed discount.
376    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    /// an utility to calculate a tax directly
384    ///
385    /// # Params
386    ///
387    /// taxable [BigDecimal] value to tax
388    ///
389    /// qty     [BigDecimal] quantity being sold (some tax needs to know this value to be calculated)
390    ///
391    /// value   [BigDecimal] percent or amount to apply as tax
392    ///
393    /// mode    [tax::Mode]  wheter the tax is percentual, amount by unit or amount by line
394    ///
395    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    /// an utility to calculate a tax directly using [String]s as entry.
404    /// Converts values to BigDecimal.
405    ///
406    /// # Params
407    ///
408    /// taxable [String] value to tax
409    ///
410    /// qty     [String] quantity being sold (some tax needs to know this value to be calculated)
411    ///
412    /// value   [String] percent or amount to apply as tax
413    ///
414    /// mode    [tax::Mode]  wheter the tax is percentual, amount by unit or amount by line
415    ///
416    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    /// an utility to calculate a tax directly using [f64]s as entry. Some precission could be loss.
425    /// Converts values to BigDecimal.
426    ///
427    /// # Params
428    ///
429    /// taxable [f64] value to tax
430    ///
431    /// qty     [f64] quantity being sold (some tax needs to know this value to be calculated)
432    ///
433    /// value   [f64] percent or amount to apply as tax
434    ///
435    /// mode    [tax::Mode]  wheter the tax is percentual, amount by unit or amount by line
436    ///
437    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    /// Creates a new [`DetailCalculator`].
453    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}