mirror of
https://github.com/duhanbalci/dreport.git
synced 2026-07-01 18:39:16 +00:00
793 lines
22 KiB
Rust
793 lines
22 KiB
Rust
use serde::{Deserialize, Serialize};
|
||
|
||
// --- Boyut sistemi ---
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(tag = "type")]
|
||
pub enum SizeValue {
|
||
#[serde(rename = "fixed")]
|
||
Fixed { value: f64 },
|
||
#[serde(rename = "auto")]
|
||
Auto,
|
||
#[serde(rename = "fr")]
|
||
Fr { value: f64 },
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct SizeConstraint {
|
||
pub width: SizeValue,
|
||
pub height: SizeValue,
|
||
pub min_width: Option<f64>,
|
||
pub min_height: Option<f64>,
|
||
pub max_width: Option<f64>,
|
||
pub max_height: Option<f64>,
|
||
}
|
||
|
||
impl Default for SizeConstraint {
|
||
fn default() -> Self {
|
||
Self {
|
||
width: SizeValue::Auto,
|
||
height: SizeValue::Auto,
|
||
min_width: None,
|
||
min_height: None,
|
||
max_width: None,
|
||
max_height: None,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct PageSettings {
|
||
pub width: f64,
|
||
pub height: f64,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
pub struct Padding {
|
||
#[serde(default)]
|
||
pub top: f64,
|
||
#[serde(default)]
|
||
pub right: f64,
|
||
#[serde(default)]
|
||
pub bottom: f64,
|
||
#[serde(default)]
|
||
pub left: f64,
|
||
}
|
||
|
||
// --- Positioning ---
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(tag = "type")]
|
||
pub enum PositionMode {
|
||
#[default]
|
||
#[serde(rename = "flow")]
|
||
Flow,
|
||
#[serde(rename = "absolute")]
|
||
Absolute { x: f64, y: f64 },
|
||
}
|
||
|
||
// --- Stil ---
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct TextStyle {
|
||
pub font_size: Option<f64>,
|
||
pub font_weight: Option<String>,
|
||
pub font_style: Option<String>,
|
||
pub font_family: Option<String>,
|
||
pub color: Option<String>,
|
||
pub align: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct LineStyle {
|
||
pub stroke_color: Option<String>,
|
||
pub stroke_width: Option<f64>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct ContainerStyle {
|
||
pub background_color: Option<String>,
|
||
pub border_color: Option<String>,
|
||
pub border_width: Option<f64>,
|
||
pub border_radius: Option<f64>,
|
||
pub border_style: Option<String>,
|
||
}
|
||
|
||
// --- Condition (v-if benzeri koşullu gösterim) ---
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct Condition {
|
||
/// Data JSON'daki alan yolu (ör: "toplamlar.iskonto")
|
||
pub path: String,
|
||
/// Karşılaştırma operatörü: eq, neq, gt, gte, lt, lte, empty, not_empty
|
||
pub operator: String,
|
||
/// Karşılaştırılacak değer (empty/not_empty için gerekmez)
|
||
#[serde(default)]
|
||
pub value: Option<serde_json::Value>,
|
||
}
|
||
|
||
// --- Binding ---
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct ScalarBinding {
|
||
pub path: String,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct ArrayBinding {
|
||
pub path: String,
|
||
}
|
||
|
||
// --- Tablo ---
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct TableColumn {
|
||
pub id: String,
|
||
pub field: String,
|
||
pub title: String,
|
||
pub width: SizeValue,
|
||
pub align: String,
|
||
pub format: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct TableStyle {
|
||
pub header_bg: Option<String>,
|
||
pub header_color: Option<String>,
|
||
pub zebra_odd: Option<String>,
|
||
pub zebra_even: Option<String>,
|
||
pub border_color: Option<String>,
|
||
pub border_width: Option<f64>,
|
||
pub font_size: Option<f64>,
|
||
pub header_font_size: Option<f64>,
|
||
/// Hücre iç boşluğu — yatay (sol+sağ), mm cinsinden. Default: 2.0
|
||
pub cell_padding_h: Option<f64>,
|
||
/// Hücre iç boşluğu — dikey (üst+alt), mm cinsinden. Default: 1.0
|
||
pub cell_padding_v: Option<f64>,
|
||
/// Header hücre iç boşluğu — yatay (sol+sağ), mm cinsinden. Default: cell_padding_h
|
||
pub header_padding_h: Option<f64>,
|
||
/// Header hücre iç boşluğu — dikey (üst+alt), mm cinsinden. Default: cell_padding_v
|
||
pub header_padding_v: Option<f64>,
|
||
}
|
||
|
||
// --- Barcode ---
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct BarcodeStyle {
|
||
pub color: Option<String>,
|
||
pub include_text: Option<bool>,
|
||
}
|
||
|
||
// --- Rich Text ---
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct RichTextSpan {
|
||
#[serde(default)]
|
||
pub text: Option<String>,
|
||
#[serde(default)]
|
||
pub binding: Option<ScalarBinding>,
|
||
#[serde(default)]
|
||
pub style: TextStyle,
|
||
}
|
||
|
||
// --- Chart ---
|
||
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "snake_case")]
|
||
pub enum ChartType {
|
||
Bar,
|
||
Line,
|
||
Pie,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "snake_case")]
|
||
pub enum GroupMode {
|
||
Grouped,
|
||
Stacked,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct ChartTitle {
|
||
pub text: String,
|
||
pub font_size: Option<f64>,
|
||
pub color: Option<String>,
|
||
pub align: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct ChartLegend {
|
||
pub show: bool,
|
||
pub position: Option<String>,
|
||
pub font_size: Option<f64>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct ChartLabels {
|
||
pub show: bool,
|
||
pub font_size: Option<f64>,
|
||
pub color: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct ChartAxis {
|
||
pub x_label: Option<String>,
|
||
pub y_label: Option<String>,
|
||
pub show_grid: Option<bool>,
|
||
pub grid_color: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct ChartStyle {
|
||
pub colors: Option<Vec<String>>,
|
||
pub background_color: Option<String>,
|
||
pub bar_gap: Option<f64>,
|
||
pub line_width: Option<f64>,
|
||
pub show_points: Option<bool>,
|
||
pub curve_type: Option<String>,
|
||
pub inner_radius: Option<f64>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct ChartElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub chart_type: ChartType,
|
||
pub data_source: ArrayBinding,
|
||
pub category_field: String,
|
||
pub value_field: String,
|
||
#[serde(default)]
|
||
pub group_field: Option<String>,
|
||
#[serde(default)]
|
||
pub group_mode: Option<GroupMode>,
|
||
#[serde(default)]
|
||
pub title: Option<ChartTitle>,
|
||
#[serde(default)]
|
||
pub legend: Option<ChartLegend>,
|
||
#[serde(default)]
|
||
pub labels: Option<ChartLabels>,
|
||
#[serde(default)]
|
||
pub axis: Option<ChartAxis>,
|
||
#[serde(default)]
|
||
pub style: ChartStyle,
|
||
}
|
||
|
||
// --- Element Base (ortak alanlar) ---
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct ElementBase {
|
||
pub id: String,
|
||
#[serde(default)]
|
||
pub condition: Option<Condition>,
|
||
#[serde(default)]
|
||
pub position: PositionMode,
|
||
#[serde(default)]
|
||
pub size: SizeConstraint,
|
||
}
|
||
|
||
impl ElementBase {
|
||
/// Flow pozisyonlu, condition'sız, verilen size ile base oluştur
|
||
pub fn flow(id: String, size: SizeConstraint) -> Self {
|
||
Self {
|
||
id,
|
||
condition: None,
|
||
position: PositionMode::Flow,
|
||
size,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub trait HasBase {
|
||
fn base(&self) -> &ElementBase;
|
||
fn base_mut(&mut self) -> &mut ElementBase;
|
||
}
|
||
|
||
macro_rules! impl_has_base {
|
||
($($t:ty),+ $(,)?) => {
|
||
$(impl HasBase for $t {
|
||
fn base(&self) -> &ElementBase { &self.base }
|
||
fn base_mut(&mut self) -> &mut ElementBase { &mut self.base }
|
||
})+
|
||
};
|
||
}
|
||
|
||
impl_has_base!(
|
||
ContainerElement,
|
||
StaticTextElement,
|
||
TextElement,
|
||
LineElement,
|
||
ImageElement,
|
||
PageNumberElement,
|
||
BarcodeElement,
|
||
RepeatingTableElement,
|
||
PageBreakElement,
|
||
CurrentDateElement,
|
||
ShapeElement,
|
||
CheckboxElement,
|
||
CalculatedTextElement,
|
||
RichTextElement,
|
||
ChartElement,
|
||
);
|
||
|
||
pub trait ElementTypeStr {
|
||
fn type_str(&self) -> &'static str;
|
||
}
|
||
|
||
macro_rules! impl_type_str {
|
||
($($t:ty => $s:literal),+ $(,)?) => {
|
||
$(impl ElementTypeStr for $t {
|
||
fn type_str(&self) -> &'static str { $s }
|
||
})+
|
||
};
|
||
}
|
||
|
||
impl_type_str!(
|
||
ContainerElement => "container",
|
||
StaticTextElement => "static_text",
|
||
TextElement => "text",
|
||
LineElement => "line",
|
||
ImageElement => "image",
|
||
PageNumberElement => "page_number",
|
||
BarcodeElement => "barcode",
|
||
RepeatingTableElement => "repeating_table",
|
||
PageBreakElement => "page_break",
|
||
CurrentDateElement => "current_date",
|
||
ShapeElement => "shape",
|
||
CheckboxElement => "checkbox",
|
||
CalculatedTextElement => "calculated_text",
|
||
RichTextElement => "rich_text",
|
||
ChartElement => "chart",
|
||
);
|
||
|
||
pub trait HasTextStyle {
|
||
fn text_style(&self) -> &TextStyle;
|
||
}
|
||
|
||
macro_rules! impl_has_text_style {
|
||
($($t:ty),+ $(,)?) => {
|
||
$(impl HasTextStyle for $t {
|
||
fn text_style(&self) -> &TextStyle { &self.style }
|
||
})+
|
||
};
|
||
}
|
||
|
||
impl_has_text_style!(
|
||
StaticTextElement,
|
||
TextElement,
|
||
PageNumberElement,
|
||
CurrentDateElement,
|
||
CalculatedTextElement,
|
||
RichTextElement,
|
||
);
|
||
|
||
pub trait HasOptionalBinding {
|
||
fn binding(&self) -> Option<&ScalarBinding>;
|
||
fn static_value(&self) -> Option<&str>;
|
||
}
|
||
|
||
impl HasOptionalBinding for ImageElement {
|
||
fn binding(&self) -> Option<&ScalarBinding> {
|
||
self.binding.as_ref()
|
||
}
|
||
fn static_value(&self) -> Option<&str> {
|
||
self.src.as_deref()
|
||
}
|
||
}
|
||
|
||
impl HasOptionalBinding for BarcodeElement {
|
||
fn binding(&self) -> Option<&ScalarBinding> {
|
||
self.binding.as_ref()
|
||
}
|
||
fn static_value(&self) -> Option<&str> {
|
||
self.value.as_deref()
|
||
}
|
||
}
|
||
|
||
// --- Element tipleri ---
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct ImageStyle {
|
||
pub object_fit: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(tag = "type")]
|
||
pub enum TemplateElement {
|
||
#[serde(rename = "container")]
|
||
Container(ContainerElement),
|
||
#[serde(rename = "static_text")]
|
||
StaticText(StaticTextElement),
|
||
#[serde(rename = "text")]
|
||
Text(TextElement),
|
||
#[serde(rename = "line")]
|
||
Line(LineElement),
|
||
#[serde(rename = "repeating_table")]
|
||
RepeatingTable(RepeatingTableElement),
|
||
#[serde(rename = "image")]
|
||
Image(ImageElement),
|
||
#[serde(rename = "page_number")]
|
||
PageNumber(PageNumberElement),
|
||
#[serde(rename = "barcode")]
|
||
Barcode(BarcodeElement),
|
||
#[serde(rename = "page_break")]
|
||
PageBreak(PageBreakElement),
|
||
#[serde(rename = "current_date")]
|
||
CurrentDate(CurrentDateElement),
|
||
#[serde(rename = "shape")]
|
||
Shape(ShapeElement),
|
||
#[serde(rename = "checkbox")]
|
||
Checkbox(CheckboxElement),
|
||
#[serde(rename = "calculated_text")]
|
||
CalculatedText(CalculatedTextElement),
|
||
#[serde(rename = "rich_text")]
|
||
RichText(RichTextElement),
|
||
#[serde(rename = "chart")]
|
||
Chart(Box<ChartElement>),
|
||
}
|
||
|
||
impl TemplateElement {
|
||
fn inner_base(&self) -> &ElementBase {
|
||
match self {
|
||
Self::Container(e) => e.base(),
|
||
Self::StaticText(e) => e.base(),
|
||
Self::Text(e) => e.base(),
|
||
Self::Line(e) => e.base(),
|
||
Self::RepeatingTable(e) => e.base(),
|
||
Self::Image(e) => e.base(),
|
||
Self::PageNumber(e) => e.base(),
|
||
Self::Barcode(e) => e.base(),
|
||
Self::PageBreak(e) => e.base(),
|
||
Self::CurrentDate(e) => e.base(),
|
||
Self::Shape(e) => e.base(),
|
||
Self::Checkbox(e) => e.base(),
|
||
Self::CalculatedText(e) => e.base(),
|
||
Self::RichText(e) => e.base(),
|
||
Self::Chart(e) => e.base(),
|
||
}
|
||
}
|
||
|
||
pub fn id(&self) -> &str {
|
||
&self.inner_base().id
|
||
}
|
||
|
||
pub fn position(&self) -> &PositionMode {
|
||
&self.inner_base().position
|
||
}
|
||
|
||
pub fn condition(&self) -> Option<&Condition> {
|
||
self.inner_base().condition.as_ref()
|
||
}
|
||
|
||
pub fn size(&self) -> &SizeConstraint {
|
||
&self.inner_base().size
|
||
}
|
||
|
||
pub fn type_str(&self) -> &'static str {
|
||
match self {
|
||
Self::Container(e) => e.type_str(),
|
||
Self::StaticText(e) => e.type_str(),
|
||
Self::Text(e) => e.type_str(),
|
||
Self::Line(e) => e.type_str(),
|
||
Self::RepeatingTable(e) => e.type_str(),
|
||
Self::Image(e) => e.type_str(),
|
||
Self::PageNumber(e) => e.type_str(),
|
||
Self::Barcode(e) => e.type_str(),
|
||
Self::PageBreak(e) => e.type_str(),
|
||
Self::CurrentDate(e) => e.type_str(),
|
||
Self::Shape(e) => e.type_str(),
|
||
Self::Checkbox(e) => e.type_str(),
|
||
Self::CalculatedText(e) => e.type_str(),
|
||
Self::RichText(e) => e.type_str(),
|
||
Self::Chart(e) => e.type_str(),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct RichTextElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
#[serde(default)]
|
||
pub style: TextStyle, // varsayilan stil (span'lar override edebilir)
|
||
pub content: Vec<RichTextSpan>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct ContainerElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
#[serde(default = "default_column")]
|
||
pub direction: String,
|
||
#[serde(default)]
|
||
pub gap: f64,
|
||
#[serde(default)]
|
||
pub padding: Padding,
|
||
#[serde(default = "default_stretch")]
|
||
pub align: String,
|
||
#[serde(default = "default_start")]
|
||
pub justify: String,
|
||
#[serde(default)]
|
||
pub style: ContainerStyle,
|
||
#[serde(default)]
|
||
pub children: Vec<TemplateElement>,
|
||
#[serde(default = "default_auto")]
|
||
pub break_inside: String,
|
||
}
|
||
|
||
fn default_auto() -> String {
|
||
"auto".to_string()
|
||
}
|
||
|
||
fn default_column() -> String {
|
||
"column".to_string()
|
||
}
|
||
fn default_stretch() -> String {
|
||
"stretch".to_string()
|
||
}
|
||
fn default_start() -> String {
|
||
"start".to_string()
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct StaticTextElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub style: TextStyle,
|
||
pub content: String,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct TextElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub style: TextStyle,
|
||
pub content: Option<String>,
|
||
pub binding: ScalarBinding,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct LineElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub style: LineStyle,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct ImageElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub src: Option<String>,
|
||
pub binding: Option<ScalarBinding>,
|
||
pub style: ImageStyle,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct PageNumberElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub style: TextStyle,
|
||
pub format: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct BarcodeElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub format: String, // qr, ean13, ean8, code128, code39
|
||
pub value: Option<String>,
|
||
pub binding: Option<ScalarBinding>,
|
||
pub style: BarcodeStyle,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct RepeatingTableElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub data_source: ArrayBinding,
|
||
pub columns: Vec<TableColumn>,
|
||
pub style: TableStyle,
|
||
#[serde(default = "default_true")]
|
||
pub repeat_header: Option<bool>,
|
||
}
|
||
|
||
fn default_true() -> Option<bool> {
|
||
Some(true)
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct PageBreakElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct CurrentDateElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub style: TextStyle,
|
||
pub format: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct ShapeElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub shape_type: String, // rectangle, ellipse, rounded_rectangle
|
||
pub style: ContainerStyle,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase", default)]
|
||
pub struct CheckboxStyle {
|
||
pub size: Option<f64>, // mm — kare boyutu
|
||
pub check_color: Option<String>, // checkmark rengi
|
||
pub border_color: Option<String>, // kare kenar rengi
|
||
pub border_width: Option<f64>, // kenar kalınlığı
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct CheckboxElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub checked: Option<bool>, // statik değer
|
||
pub binding: Option<ScalarBinding>, // dinamik boolean binding
|
||
pub style: CheckboxStyle,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct CalculatedTextElement {
|
||
#[serde(flatten)]
|
||
pub base: ElementBase,
|
||
pub style: TextStyle,
|
||
pub expression: String,
|
||
pub format: Option<String>,
|
||
}
|
||
|
||
// --- Template ---
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct Template {
|
||
pub id: String,
|
||
pub name: String,
|
||
pub page: PageSettings,
|
||
pub fonts: Vec<String>,
|
||
#[serde(default)]
|
||
pub header: Option<ContainerElement>,
|
||
#[serde(default)]
|
||
pub footer: Option<ContainerElement>,
|
||
pub root: ContainerElement,
|
||
#[serde(default)]
|
||
pub format_config: Option<FormatConfig>,
|
||
/// Lokalizasyon: "tr-TR", "en-US", "de-DE", "fr-FR" vb.
|
||
/// Belirtilirse ve format_config yoksa, locale'den FormatConfig türetilir.
|
||
#[serde(default)]
|
||
pub locale: Option<String>,
|
||
}
|
||
|
||
/// Sayı/para birimi formatlama ayarları.
|
||
/// Belirtilmezse Türk Lirası varsayılan (. binlik, , ondalık, ₺ sembol).
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||
pub struct FormatConfig {
|
||
/// Binlik ayırıcı (varsayılan ".")
|
||
#[serde(default = "FormatConfig::default_thousands_sep")]
|
||
pub thousands_separator: String,
|
||
/// Ondalık ayırıcı (varsayılan ",")
|
||
#[serde(default = "FormatConfig::default_decimal_sep")]
|
||
pub decimal_separator: String,
|
||
/// Para birimi sembolü (varsayılan "₺")
|
||
#[serde(default = "FormatConfig::default_currency_symbol")]
|
||
pub currency_symbol: String,
|
||
/// Para birimi sembolü pozisyonu: "suffix" (varsayılan) veya "prefix"
|
||
#[serde(default = "FormatConfig::default_currency_position")]
|
||
pub currency_position: String,
|
||
}
|
||
|
||
impl FormatConfig {
|
||
fn default_thousands_sep() -> String {
|
||
".".to_string()
|
||
}
|
||
fn default_decimal_sep() -> String {
|
||
",".to_string()
|
||
}
|
||
fn default_currency_symbol() -> String {
|
||
"₺".to_string()
|
||
}
|
||
fn default_currency_position() -> String {
|
||
"suffix".to_string()
|
||
}
|
||
}
|
||
|
||
impl Default for FormatConfig {
|
||
fn default() -> Self {
|
||
Self {
|
||
thousands_separator: Self::default_thousands_sep(),
|
||
decimal_separator: Self::default_decimal_sep(),
|
||
currency_symbol: Self::default_currency_symbol(),
|
||
currency_position: Self::default_currency_position(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl FormatConfig {
|
||
/// Locale string'inden FormatConfig türet.
|
||
/// Desteklenen locale'ler: tr-TR, en-US, de-DE, fr-FR.
|
||
/// Bilinmeyen locale → Türk formatı (varsayılan).
|
||
pub fn from_locale(locale: &str) -> Self {
|
||
match locale {
|
||
"en-US" | "en" => Self {
|
||
thousands_separator: ",".to_string(),
|
||
decimal_separator: ".".to_string(),
|
||
currency_symbol: "$".to_string(),
|
||
currency_position: "prefix".to_string(),
|
||
},
|
||
"de-DE" | "de" => Self {
|
||
thousands_separator: ".".to_string(),
|
||
decimal_separator: ",".to_string(),
|
||
currency_symbol: "€".to_string(),
|
||
currency_position: "suffix".to_string(),
|
||
},
|
||
"fr-FR" | "fr" => Self {
|
||
thousands_separator: " ".to_string(),
|
||
decimal_separator: ",".to_string(),
|
||
currency_symbol: "€".to_string(),
|
||
currency_position: "suffix".to_string(),
|
||
},
|
||
"en-GB" | "gb" => Self {
|
||
thousands_separator: ",".to_string(),
|
||
decimal_separator: ".".to_string(),
|
||
currency_symbol: "£".to_string(),
|
||
currency_position: "prefix".to_string(),
|
||
},
|
||
// tr-TR veya bilinmeyen → Türk formatı
|
||
_ => Self::default(),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Template {
|
||
/// Template'in etkin FormatConfig'ini döndür.
|
||
/// Öncelik: format_config > locale > varsayılan (tr-TR).
|
||
pub fn effective_format_config(&self) -> FormatConfig {
|
||
if let Some(ref fc) = self.format_config {
|
||
fc.clone()
|
||
} else if let Some(ref locale) = self.locale {
|
||
FormatConfig::from_locale(locale)
|
||
} else {
|
||
FormatConfig::default()
|
||
}
|
||
}
|
||
}
|