use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
#[cfg(feature = "openapi")]
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
use crate::domain::entity::StockItem;
use crate::domain::entity::AuditMetadata;
use crate::domain::entity::ServiceTrackingType;
use crate::domain::entity::ValuationMethod;
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct CreateStockItemDto {
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "item_id")]
pub item_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "stock_uom")]
pub stock_uom: String,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "is_stock_item")]
pub is_stock_item: bool,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "has_batch")]
pub has_batch: bool,
#[serde(alias = "valuation_method")]
pub valuation_method: ValuationMethod,
#[serde(alias = "reorder_level")]
pub reorder_level: Decimal,
#[serde(alias = "weight_per_unit")]
pub weight_per_unit: Decimal,
#[serde(alias = "service_tracking")]
pub service_tracking: ServiceTrackingType,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "service_project_id")]
pub service_project_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "service_project_template_id")]
pub service_project_template_id: Option<Uuid>,
}
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct UpdateStockItemDto {
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "item_id")]
pub item_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "stock_uom")]
pub stock_uom: String,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "is_stock_item")]
pub is_stock_item: bool,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "has_batch")]
pub has_batch: bool,
#[serde(alias = "valuation_method")]
pub valuation_method: ValuationMethod,
#[serde(alias = "reorder_level")]
pub reorder_level: Decimal,
#[serde(alias = "weight_per_unit")]
pub weight_per_unit: Decimal,
#[serde(alias = "service_tracking")]
pub service_tracking: ServiceTrackingType,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "service_project_id")]
pub service_project_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "service_project_template_id")]
pub service_project_template_id: Option<Uuid>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct PatchStockItemDto {
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "item_id")]
pub item_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "stock_uom")]
pub stock_uom: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "is_stock_item")]
pub is_stock_item: Option<bool>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "has_batch")]
pub has_batch: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", alias = "valuation_method")]
pub valuation_method: Option<ValuationMethod>,
#[serde(skip_serializing_if = "Option::is_none", alias = "reorder_level")]
pub reorder_level: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "weight_per_unit")]
pub weight_per_unit: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "service_tracking")]
pub service_tracking: Option<ServiceTrackingType>,
#[serde(skip_serializing_if = "Option::is_none", alias = "service_project_id")]
pub service_project_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "service_project_template_id")]
pub service_project_template_id: Option<Uuid>,
}
impl PatchStockItemDto {
pub fn has_changes(&self) -> bool {
self.item_id.is_some() || self.stock_uom.is_some() || self.is_stock_item.is_some() || self.has_batch.is_some() || self.valuation_method.is_some() || self.reorder_level.is_some() || self.weight_per_unit.is_some() || self.service_tracking.is_some() || self.service_project_id.is_some() || self.service_project_template_id.is_some()
}
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct StockItemResponseDto {
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub item_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub stock_uom: String,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub is_stock_item: bool,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub has_batch: bool,
pub valuation_method: ValuationMethod,
pub reorder_level: Decimal,
pub weight_per_unit: Decimal,
pub service_tracking: ServiceTrackingType,
pub service_project_id: Option<Uuid>,
pub service_project_template_id: Option<Uuid>,
pub metadata: AuditMetadata,
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct StockItemListResponseDto {
pub items: Vec<StockItemResponseDto>,
pub total: u64,
pub page: u32,
pub per_page: u32,
pub total_pages: u32,
pub has_next: bool,
pub has_prev: bool,
}
impl StockItemListResponseDto {
pub fn new(items: Vec<StockItemResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
let total_pages = if per_page > 0 {
((total as f64) / (per_page as f64)).ceil() as u32
} else {
0
};
Self {
items,
total,
page,
per_page,
total_pages,
has_next: page < total_pages,
has_prev: page > 1,
}
}
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct StockItemSummaryDto {
pub id: Uuid,
pub item_id: Uuid,
pub stock_uom: String,
pub is_stock_item: bool,
pub created_at: Option<DateTime<Utc>>,
}
impl From<StockItem> for StockItemResponseDto {
fn from(entity: StockItem) -> Self {
Self {
id: entity.id,
item_id: entity.item_id,
stock_uom: entity.stock_uom,
is_stock_item: entity.is_stock_item,
has_batch: entity.has_batch,
valuation_method: entity.valuation_method,
reorder_level: entity.reorder_level,
weight_per_unit: entity.weight_per_unit,
service_tracking: entity.service_tracking,
service_project_id: entity.service_project_id,
service_project_template_id: entity.service_project_template_id,
metadata: entity.metadata,
}
}
}
impl From<StockItem> for StockItemSummaryDto {
fn from(entity: StockItem) -> Self {
let created_at = backbone_core::PersistentEntity::created_at(&entity);
Self {
id: entity.id,
item_id: entity.item_id,
stock_uom: entity.stock_uom,
is_stock_item: entity.is_stock_item,
created_at,
}
}
}
impl From<CreateStockItemDto> for StockItem {
fn from(dto: CreateStockItemDto) -> Self {
Self {
id: Uuid::new_v4(),
item_id: dto.item_id,
stock_uom: dto.stock_uom,
is_stock_item: dto.is_stock_item,
has_batch: dto.has_batch,
valuation_method: dto.valuation_method,
reorder_level: dto.reorder_level,
weight_per_unit: dto.weight_per_unit,
service_tracking: dto.service_tracking,
service_project_id: dto.service_project_id,
service_project_template_id: dto.service_project_template_id,
metadata: AuditMetadata::default(),
}
}
}
impl From<&StockItem> for StockItemResponseDto {
fn from(entity: &StockItem) -> Self {
Self {
id: entity.id.clone(),
item_id: entity.item_id.clone(),
stock_uom: entity.stock_uom.clone(),
is_stock_item: entity.is_stock_item.clone(),
has_batch: entity.has_batch.clone(),
valuation_method: entity.valuation_method.clone(),
reorder_level: entity.reorder_level.clone(),
weight_per_unit: entity.weight_per_unit.clone(),
service_tracking: entity.service_tracking.clone(),
service_project_id: entity.service_project_id.clone(),
service_project_template_id: entity.service_project_template_id.clone(),
metadata: entity.metadata.clone(),
}
}
}
impl backbone_core::FromCreateDto<CreateStockItemDto> for StockItem {
fn from_create_dto(dto: CreateStockItemDto) -> backbone_core::ServiceResult<Self> {
Ok(StockItem::from(dto))
}
}
impl backbone_core::ApplyUpdateDto<UpdateStockItemDto> for StockItem {
fn apply_update(mut self, dto: UpdateStockItemDto) -> backbone_core::ServiceResult<Self> {
self.item_id = dto.item_id;
self.stock_uom = dto.stock_uom;
self.is_stock_item = dto.is_stock_item;
self.has_batch = dto.has_batch;
self.valuation_method = dto.valuation_method;
self.reorder_level = dto.reorder_level;
self.weight_per_unit = dto.weight_per_unit;
self.service_tracking = dto.service_tracking;
self.service_project_id = dto.service_project_id;
self.service_project_template_id = dto.service_project_template_id;
Ok(self)
}
}