use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn derive_brazilian_tax_entity(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
eprintln!("[pleme-codegen] BrazilianTaxEntity pattern applied to {} - saving ~30 lines", struct_name);
let expanded = quote! {
impl #struct_name {
pub fn calculate_icms(&self, subtotal: rust_decimal::Decimal, state: &str) -> rust_decimal::Decimal {
use rust_decimal::Decimal;
let tax_rate = match state.to_uppercase().as_str() {
"SP" => Decimal::new(18, 2), "RJ" => Decimal::new(20, 2), "MG" => Decimal::new(18, 2), "RS" => Decimal::new(17, 2), "PR" => Decimal::new(19, 2), "SC" => Decimal::new(17, 2), "BA" => Decimal::new(19, 2), "PE" => Decimal::new(18, 2), "CE" => Decimal::new(19, 2), "DF" => Decimal::new(18, 2), "GO" => Decimal::new(17, 2), "MT" => Decimal::new(17, 2), "MS" => Decimal::new(17, 2), "ES" => Decimal::new(17, 2), "PA" => Decimal::new(19, 2), "AM" => Decimal::new(20, 2), "MA" => Decimal::new(19, 2), "PI" => Decimal::new(19, 2), "RN" => Decimal::new(18, 2), "PB" => Decimal::new(18, 2), "AL" => Decimal::new(19, 2), "SE" => Decimal::new(19, 2), "TO" => Decimal::new(18, 2), "RO" => Decimal::new(17, 2), "RR" => Decimal::new(17, 2), "AC" => Decimal::new(17, 2), "AP" => Decimal::new(18, 2), _ => Decimal::new(17, 2), };
let icms = subtotal * tax_rate / Decimal::new(100, 0);
tracing::debug!(
entity = %stringify!(#struct_name),
subtotal = %subtotal,
state = %state,
tax_rate = %tax_rate,
icms = %icms,
"ICMS calculated"
);
icms
}
pub fn calculate_pis(&self, subtotal: rust_decimal::Decimal) -> rust_decimal::Decimal {
let pis_rate = rust_decimal::Decimal::new(165, 4); subtotal * pis_rate / rust_decimal::Decimal::new(100, 0)
}
pub fn calculate_cofins(&self, subtotal: rust_decimal::Decimal) -> rust_decimal::Decimal {
let cofins_rate = rust_decimal::Decimal::new(760, 4); subtotal * cofins_rate / rust_decimal::Decimal::new(100, 0)
}
pub fn calculate_iss(&self, subtotal: rust_decimal::Decimal, city: &str) -> rust_decimal::Decimal {
use rust_decimal::Decimal;
let iss_rate = match city.to_uppercase().as_str() {
"SAO PAULO" | "SP" => Decimal::new(5, 2), "RIO DE JANEIRO" | "RJ" => Decimal::new(5, 2), "BELO HORIZONTE" | "BH" => Decimal::new(3, 2), "CURITIBA" => Decimal::new(2, 2), _ => Decimal::new(3, 2), };
subtotal * iss_rate / Decimal::new(100, 0)
}
pub fn calculate_total_tax(&self, subtotal: rust_decimal::Decimal, state: &str, is_service: bool) -> rust_decimal::Decimal {
if is_service {
let iss = self.calculate_iss(subtotal, state);
let pis = self.calculate_pis(subtotal);
let cofins = self.calculate_cofins(subtotal);
iss + pis + cofins
} else {
let icms = self.calculate_icms(subtotal, state);
let pis = self.calculate_pis(subtotal);
let cofins = self.calculate_cofins(subtotal);
icms + pis + cofins
}
}
pub fn generate_nfe_key(&self) -> String {
let timestamp = chrono::Utc::now();
let random = uuid::Uuid::new_v4().to_string()[..8].to_uppercase();
format!("NFE-{}-{}", timestamp.format("%Y%m%d%H%M%S"), random)
}
}
};
TokenStream::from(expanded)
}
pub fn derive_shipping_entity(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
eprintln!("[pleme-codegen] ShippingEntity pattern applied to {} - saving ~25 lines", struct_name);
let expanded = quote! {
impl #struct_name {
pub fn calculate_shipping_cost(&self, items_count: i32, weight_kg: f64, origin_state: &str, dest_state: &str, country: &str) -> rust_decimal::Decimal {
use rust_decimal::Decimal;
if country.to_uppercase() != "BR" {
return Decimal::new(5000, 2); }
let base_cost = Decimal::new(1500, 2); let weight_cost = Decimal::from_f64_retain(weight_kg * 5.0).unwrap_or(Decimal::ZERO);
let zone_multiplier = self.calculate_zone_multiplier(origin_state, dest_state);
let subtotal = base_cost + weight_cost;
let total = subtotal * zone_multiplier / Decimal::new(100, 0);
tracing::info!(
entity = %stringify!(#struct_name),
items = %items_count,
weight_kg = %weight_kg,
origin = %origin_state,
destination = %dest_state,
cost = %total,
"Shipping cost calculated"
);
total
}
fn calculate_zone_multiplier(&self, origin: &str, dest: &str) -> rust_decimal::Decimal {
use rust_decimal::Decimal;
if origin.to_uppercase() == dest.to_uppercase() {
return Decimal::new(100, 2);
}
let southeast = ["SP", "RJ", "MG", "ES"];
let south = ["PR", "SC", "RS"];
let northeast = ["BA", "SE", "AL", "PE", "PB", "RN", "CE", "PI", "MA"];
let north = ["AC", "AP", "AM", "PA", "RO", "RR", "TO"];
let center = ["GO", "MT", "MS", "DF"];
let origin_upper = origin.to_uppercase();
let dest_upper = dest.to_uppercase();
if (southeast.contains(&origin_upper.as_str()) && southeast.contains(&dest_upper.as_str())) ||
(south.contains(&origin_upper.as_str()) && south.contains(&dest_upper.as_str())) ||
(northeast.contains(&origin_upper.as_str()) && northeast.contains(&dest_upper.as_str())) ||
(north.contains(&origin_upper.as_str()) && north.contains(&dest_upper.as_str())) ||
(center.contains(&origin_upper.as_str()) && center.contains(&dest_upper.as_str())) {
return Decimal::new(120, 2);
}
if (southeast.contains(&origin_upper.as_str()) && (south.contains(&dest_upper.as_str()) || center.contains(&dest_upper.as_str()))) ||
(south.contains(&origin_upper.as_str()) && southeast.contains(&dest_upper.as_str())) {
return Decimal::new(150, 2);
}
if (southeast.contains(&origin_upper.as_str()) && north.contains(&dest_upper.as_str())) ||
(north.contains(&origin_upper.as_str()) && southeast.contains(&dest_upper.as_str())) ||
(south.contains(&origin_upper.as_str()) && north.contains(&dest_upper.as_str())) ||
(north.contains(&origin_upper.as_str()) && south.contains(&dest_upper.as_str())) {
return Decimal::new(180, 2);
}
Decimal::new(160, 2)
}
pub fn estimate_delivery_days(&self, origin_state: &str, dest_state: &str, service_type: &str) -> u32 {
if origin_state == dest_state && service_type == "express" {
return 1;
}
if origin_state == dest_state {
return match service_type {
"express" => 1,
"standard" => 2,
_ => 3,
};
}
let multiplier = self.calculate_zone_multiplier(origin_state, dest_state);
let base_days = match service_type {
"express" => 2,
"standard" => 5,
_ => 7,
};
use rust_decimal::prelude::ToPrimitive;
let factor = multiplier.to_f64().unwrap_or(1.0) / 100.0;
(base_days as f64 * factor).ceil() as u32
}
pub fn recommend_carrier(&self, origin: &str, dest: &str, weight_kg: f64) -> &'static str {
if origin == dest {
return "Local Courier";
}
if weight_kg < 1.0 {
"Correios PAC Mini"
} else if weight_kg < 30.0 {
"Correios PAC"
} else if weight_kg < 100.0 {
"Transportadora Regional"
} else {
"Transportadora Pesada"
}
}
}
};
TokenStream::from(expanded)
}