number_description.rs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. use crate::impl_from_and_to_type_option;
  2. use crate::services::row::StringifyCellData;
  3. use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
  4. use flowy_error::FlowyError;
  5. use flowy_grid_data_model::entities::{Field, FieldType};
  6. use lazy_static::lazy_static;
  7. use rust_decimal::prelude::Zero;
  8. use rust_decimal::Decimal;
  9. use rusty_money::iso::{Currency, CNY, EUR, USD};
  10. use serde::{Deserialize, Serialize};
  11. use std::str::FromStr;
  12. use strum::IntoEnumIterator;
  13. use strum_macros::EnumIter;
  14. lazy_static! {
  15. static ref STRIP_SYMBOL: Vec<String> = make_strip_symbol();
  16. }
  17. #[derive(Clone, Copy, Debug, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
  18. pub enum NumberFormat {
  19. Number = 0,
  20. USD = 1,
  21. CNY = 2,
  22. EUR = 3,
  23. }
  24. impl std::default::Default for NumberFormat {
  25. fn default() -> Self {
  26. NumberFormat::Number
  27. }
  28. }
  29. impl NumberFormat {
  30. pub fn symbol(&self) -> String {
  31. match self {
  32. NumberFormat::Number => "".to_string(),
  33. NumberFormat::USD => USD.symbol.to_string(),
  34. NumberFormat::CNY => CNY.symbol.to_string(),
  35. NumberFormat::EUR => EUR.symbol.to_string(),
  36. }
  37. }
  38. #[allow(dead_code)]
  39. pub fn code(&self) -> String {
  40. match self {
  41. NumberFormat::Number => "".to_string(),
  42. NumberFormat::USD => USD.iso_alpha_code.to_string(),
  43. NumberFormat::CNY => CNY.iso_alpha_code.to_string(),
  44. NumberFormat::EUR => EUR.iso_alpha_code.to_string(),
  45. }
  46. }
  47. }
  48. // Number
  49. #[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
  50. pub struct NumberDescription {
  51. #[pb(index = 1)]
  52. pub format: NumberFormat,
  53. #[pb(index = 2)]
  54. pub scale: u32,
  55. #[pb(index = 3)]
  56. pub symbol: String,
  57. #[pb(index = 4)]
  58. pub sign_positive: bool,
  59. #[pb(index = 5)]
  60. pub name: String,
  61. }
  62. impl_from_and_to_type_option!(NumberDescription, FieldType::Number);
  63. impl std::default::Default for NumberDescription {
  64. fn default() -> Self {
  65. let format = NumberFormat::default();
  66. let symbol = format.symbol();
  67. NumberDescription {
  68. format,
  69. scale: 0,
  70. symbol,
  71. sign_positive: true,
  72. name: "Number".to_string(),
  73. }
  74. }
  75. }
  76. impl NumberDescription {
  77. pub fn set_format(&mut self, format: NumberFormat) {
  78. self.format = format;
  79. self.symbol = format.symbol();
  80. }
  81. fn decimal_from_str(&self, s: &str) -> Decimal {
  82. let mut decimal = Decimal::from_str(s).unwrap_or(Decimal::zero());
  83. match decimal.set_scale(self.scale) {
  84. Ok(_) => {}
  85. Err(e) => {
  86. tracing::error!("Set decimal scale failed: {:?}", e);
  87. }
  88. }
  89. decimal.set_sign_positive(self.sign_positive);
  90. decimal
  91. }
  92. fn money_from_str(&self, s: &str, currency: &'static Currency) -> String {
  93. let decimal = self.decimal_from_str(s);
  94. let money = rusty_money::Money::from_decimal(decimal, currency);
  95. money.to_string()
  96. }
  97. fn strip_symbol(&self, s: &str) -> String {
  98. let mut s = String::from(s);
  99. if !s.chars().all(char::is_numeric) {
  100. s.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
  101. }
  102. s
  103. }
  104. }
  105. impl StringifyCellData for NumberDescription {
  106. fn str_from_cell_data(&self, data: String) -> String {
  107. match self.format {
  108. NumberFormat::Number => data,
  109. NumberFormat::USD => self.money_from_str(&data, USD),
  110. NumberFormat::CNY => self.money_from_str(&data, CNY),
  111. NumberFormat::EUR => self.money_from_str(&data, EUR),
  112. }
  113. }
  114. fn str_to_cell_data(&self, s: &str) -> Result<String, FlowyError> {
  115. Ok(self.strip_symbol(s))
  116. }
  117. }
  118. fn make_strip_symbol() -> Vec<String> {
  119. let mut symbols = vec![",".to_owned(), ".".to_owned()];
  120. for format in NumberFormat::iter() {
  121. symbols.push(format.symbol());
  122. }
  123. symbols
  124. }
  125. #[cfg(test)]
  126. mod tests {
  127. use crate::services::cell::{NumberDescription, NumberFormat};
  128. use crate::services::row::StringifyCellData;
  129. use strum::IntoEnumIterator;
  130. #[test]
  131. fn number_description_test() {
  132. let mut description = NumberDescription::default();
  133. assert_eq!(description.str_to_cell_data("¥18,443").unwrap(), "18443".to_owned());
  134. assert_eq!(description.str_to_cell_data("$18,443").unwrap(), "18443".to_owned());
  135. assert_eq!(description.str_to_cell_data("€18.443").unwrap(), "18443".to_owned());
  136. for format in NumberFormat::iter() {
  137. description.format = format;
  138. match format {
  139. NumberFormat::Number => {
  140. assert_eq!(description.str_from_cell_data("18443".to_owned()), "18443".to_owned());
  141. }
  142. NumberFormat::USD => {
  143. assert_eq!(description.str_from_cell_data("18443".to_owned()), "$18,443".to_owned());
  144. }
  145. NumberFormat::CNY => {
  146. assert_eq!(description.str_from_cell_data("18443".to_owned()), "¥18,443".to_owned());
  147. }
  148. NumberFormat::EUR => {
  149. assert_eq!(description.str_from_cell_data("18443".to_owned()), "€18.443".to_owned());
  150. }
  151. }
  152. }
  153. }
  154. #[test]
  155. fn number_description_scale_test() {
  156. let mut description = NumberDescription::default();
  157. description.scale = 1;
  158. for format in NumberFormat::iter() {
  159. description.format = format;
  160. match format {
  161. NumberFormat::Number => {
  162. assert_eq!(description.str_from_cell_data("18443".to_owned()), "18443".to_owned());
  163. }
  164. NumberFormat::USD => {
  165. assert_eq!(
  166. description.str_from_cell_data("18443".to_owned()),
  167. "$1,844.3".to_owned()
  168. );
  169. }
  170. NumberFormat::CNY => {
  171. assert_eq!(
  172. description.str_from_cell_data("18443".to_owned()),
  173. "¥1,844.3".to_owned()
  174. );
  175. }
  176. NumberFormat::EUR => {
  177. assert_eq!(
  178. description.str_from_cell_data("18443".to_owned()),
  179. "€1.844,3".to_owned()
  180. );
  181. }
  182. }
  183. }
  184. }
  185. #[test]
  186. fn number_description_sign_test() {
  187. let mut description = NumberDescription::default();
  188. description.sign_positive = false;
  189. for format in NumberFormat::iter() {
  190. description.format = format;
  191. match format {
  192. NumberFormat::Number => {
  193. assert_eq!(description.str_from_cell_data("18443".to_owned()), "18443".to_owned());
  194. }
  195. NumberFormat::USD => {
  196. assert_eq!(
  197. description.str_from_cell_data("18443".to_owned()),
  198. "-$18,443".to_owned()
  199. );
  200. }
  201. NumberFormat::CNY => {
  202. assert_eq!(
  203. description.str_from_cell_data("18443".to_owned()),
  204. "-¥18,443".to_owned()
  205. );
  206. }
  207. NumberFormat::EUR => {
  208. assert_eq!(
  209. description.str_from_cell_data("18443".to_owned()),
  210. "-€18.443".to_owned()
  211. );
  212. }
  213. }
  214. }
  215. }
  216. }