313 lines
8.8 KiB
TypeScript
313 lines
8.8 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import type { Cart, CartItem, Company, Product } from '@/types'
|
|
import { generateId } from '@/utils/format'
|
|
import { validateMeterage } from '@/utils/validation'
|
|
import { getItem, setItem, STORAGE_KEYS } from '@/services/storage'
|
|
import { getCompanyById } from '@/data/mockData'
|
|
|
|
const DEFAULT_MAX_METERAGE = 5000
|
|
|
|
function loadCarts(): Cart[] {
|
|
return getItem<Cart[]>(STORAGE_KEYS.carts, [])
|
|
}
|
|
|
|
function saveCarts(carts: Cart[]) {
|
|
setItem(STORAGE_KEYS.carts, carts)
|
|
}
|
|
|
|
function loadActiveCartId(): string | null {
|
|
return getItem<string | null>(STORAGE_KEYS.activeCartId, null)
|
|
}
|
|
|
|
function saveActiveCartId(id: string | null) {
|
|
setItem(STORAGE_KEYS.activeCartId, id)
|
|
}
|
|
|
|
function getCartTotalMeterage(cart: Cart): number {
|
|
return cart.items.reduce((sum, item) => sum + item.meterage, 0)
|
|
}
|
|
|
|
function companyMeterage(cart: Cart, companyId: string): number {
|
|
return cart.items
|
|
.filter((item) => item.companyId === companyId)
|
|
.reduce((sum, item) => sum + item.meterage, 0)
|
|
}
|
|
|
|
function uniqueCompanyNames(items: CartItem[]): string {
|
|
const names = [...new Set(items.map((item) => item.companyName).filter(Boolean))]
|
|
return names.join('، ')
|
|
}
|
|
|
|
function syncCartCompanyMeta(cart: Cart) {
|
|
if (cart.items.length === 0) return
|
|
const first = cart.items[0]!
|
|
cart.companyId = first.companyId
|
|
cart.companyName = uniqueCompanyNames(cart.items) || first.companyName
|
|
}
|
|
|
|
function migrateCartItems(carts: Cart[]): Cart[] {
|
|
return carts.map((cart) => ({
|
|
...cart,
|
|
items: cart.items.map((item) => ({
|
|
...item,
|
|
companyId: item.companyId || cart.companyId,
|
|
companyName: item.companyName || cart.companyName,
|
|
})),
|
|
}))
|
|
}
|
|
|
|
export const useCartStore = defineStore('cart', () => {
|
|
const carts = ref<Cart[]>(migrateCartItems(loadCarts()))
|
|
const activeCartId = ref<string | null>(loadActiveCartId())
|
|
const selectedCompanyId = ref<string | null>(null)
|
|
|
|
const activeCart = computed(() => carts.value.find((c) => c.id === activeCartId.value) ?? null)
|
|
|
|
const totalMeterage = computed(
|
|
() => activeCart.value?.items.reduce((sum, i) => sum + i.meterage, 0) ?? 0,
|
|
)
|
|
|
|
const itemCount = computed(() => activeCart.value?.items.length ?? 0)
|
|
|
|
const companyNamesLabel = computed(() => {
|
|
if (!activeCart.value?.items.length) return ''
|
|
return uniqueCompanyNames(activeCart.value.items)
|
|
})
|
|
|
|
const maxMeterage = computed(() => {
|
|
if (!activeCart.value?.items.length) {
|
|
const selected = selectedCompanyId.value
|
|
? getCompanyById(selectedCompanyId.value)
|
|
: null
|
|
return selected?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE
|
|
}
|
|
|
|
const companyIds = [...new Set(activeCart.value.items.map((item) => item.companyId))]
|
|
return companyIds.reduce((sum, id) => {
|
|
const company = getCompanyById(id)
|
|
return sum + (company?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE)
|
|
}, 0)
|
|
})
|
|
|
|
const remainingCapacity = computed(() => Math.max(0, maxMeterage.value - totalMeterage.value))
|
|
|
|
const isFull = computed(() => totalMeterage.value >= maxMeterage.value)
|
|
|
|
function persist() {
|
|
saveCarts(carts.value)
|
|
saveActiveCartId(activeCartId.value)
|
|
}
|
|
|
|
function createEmptyCart(company?: Company): Cart {
|
|
const cart: Cart = {
|
|
id: generateId(),
|
|
companyId: company?.id ?? '',
|
|
companyName: company?.name ?? '',
|
|
items: [],
|
|
}
|
|
carts.value.push(cart)
|
|
activeCartId.value = cart.id
|
|
if (company) selectedCompanyId.value = company.id
|
|
persist()
|
|
return cart
|
|
}
|
|
|
|
function getOrCreateCart(company?: Company): Cart {
|
|
if (activeCart.value) return activeCart.value
|
|
|
|
const withItems = carts.value.find((cart) => cart.items.length > 0)
|
|
if (withItems) {
|
|
activeCartId.value = withItems.id
|
|
selectedCompanyId.value = selectedCompanyId.value ?? withItems.companyId
|
|
persist()
|
|
return withItems
|
|
}
|
|
|
|
const open = carts.value.find(
|
|
(cart) => getCartTotalMeterage(cart) < DEFAULT_MAX_METERAGE,
|
|
)
|
|
if (open) {
|
|
activeCartId.value = open.id
|
|
if (company) selectedCompanyId.value = company.id
|
|
persist()
|
|
return open
|
|
}
|
|
|
|
return createEmptyCart(company)
|
|
}
|
|
|
|
/** @deprecated Prefer getOrCreateCart / selectCompany — kept for callers. */
|
|
function initCartForCompany(company: Company): Cart {
|
|
selectedCompanyId.value = company.id
|
|
const cart = getOrCreateCart(company)
|
|
if (!cart.companyId) {
|
|
cart.companyId = company.id
|
|
cart.companyName = company.name
|
|
persist()
|
|
}
|
|
return cart
|
|
}
|
|
|
|
function selectCompany(company: Company): void {
|
|
selectedCompanyId.value = company.id
|
|
getOrCreateCart(company)
|
|
}
|
|
|
|
function createNewCartForSameCompany(): Cart | null {
|
|
const company = selectedCompanyId.value
|
|
? getCompanyById(selectedCompanyId.value)
|
|
: activeCart.value?.companyId
|
|
? getCompanyById(activeCart.value.companyId)
|
|
: undefined
|
|
return createEmptyCart(company)
|
|
}
|
|
|
|
function addItem(
|
|
product: Product,
|
|
catalogCode: string,
|
|
meterage: number,
|
|
): { success: boolean; message?: string } {
|
|
const company = getCompanyById(product.companyId)
|
|
if (!company) {
|
|
return { success: false, message: 'شرکت محصول یافت نشد.' }
|
|
}
|
|
|
|
const cart = getOrCreateCart(company)
|
|
selectedCompanyId.value = company.id
|
|
|
|
const companyMax = company.maxMeteragePerInvoice
|
|
const currentCompanyMeterage = companyMeterage(cart, company.id)
|
|
const existing = cart.items.find((i) => i.productId === product.id)
|
|
const previousMeterage = existing?.meterage ?? 0
|
|
|
|
const validation = validateMeterage(
|
|
meterage,
|
|
product,
|
|
currentCompanyMeterage,
|
|
companyMax,
|
|
previousMeterage,
|
|
)
|
|
if (!validation.valid) {
|
|
return { success: false, message: validation.message }
|
|
}
|
|
|
|
if (existing) {
|
|
existing.meterage = meterage
|
|
existing.catalogCode = catalogCode
|
|
existing.companyId = company.id
|
|
existing.companyName = company.name
|
|
} else {
|
|
cart.items.push({
|
|
id: generateId(),
|
|
productId: product.id,
|
|
catalogCode,
|
|
productName: product.name,
|
|
productCode: product.code,
|
|
color: product.color,
|
|
meterage,
|
|
unitPrice: product.basePrice,
|
|
companyId: company.id,
|
|
companyName: company.name,
|
|
})
|
|
}
|
|
|
|
syncCartCompanyMeta(cart)
|
|
persist()
|
|
return { success: true }
|
|
}
|
|
|
|
function updateItemMeterage(
|
|
itemId: string,
|
|
meterage: number,
|
|
product: Product,
|
|
): { success: boolean; message?: string } {
|
|
if (!activeCart.value) return { success: false, message: 'سبد خرید یافت نشد.' }
|
|
const item = activeCart.value.items.find((i) => i.id === itemId)
|
|
if (!item) return { success: false, message: 'آیتم یافت نشد.' }
|
|
|
|
const company = getCompanyById(item.companyId || product.companyId)
|
|
const companyMax = company?.maxMeteragePerInvoice ?? DEFAULT_MAX_METERAGE
|
|
const currentCompanyMeterage = companyMeterage(activeCart.value, item.companyId)
|
|
|
|
const validation = validateMeterage(
|
|
meterage,
|
|
product,
|
|
currentCompanyMeterage,
|
|
companyMax,
|
|
item.meterage,
|
|
)
|
|
if (!validation.valid) {
|
|
return { success: false, message: validation.message }
|
|
}
|
|
|
|
item.meterage = meterage
|
|
persist()
|
|
return { success: true }
|
|
}
|
|
|
|
function removeItem(itemId: string) {
|
|
if (!activeCart.value) return
|
|
activeCart.value.items = activeCart.value.items.filter((i) => i.id !== itemId)
|
|
syncCartCompanyMeta(activeCart.value)
|
|
persist()
|
|
}
|
|
|
|
function setNotes(notes: string) {
|
|
if (!activeCart.value) return
|
|
activeCart.value.notes = notes
|
|
persist()
|
|
}
|
|
|
|
function clearActiveCart() {
|
|
if (!activeCart.value) return
|
|
activeCart.value.items = []
|
|
activeCart.value.notes = undefined
|
|
persist()
|
|
}
|
|
|
|
function ensureActiveCartForCartPage() {
|
|
if (activeCart.value?.items.length) return
|
|
|
|
const cartWithItems = carts.value.find((cart) => cart.items.length > 0)
|
|
if (!cartWithItems) return
|
|
|
|
activeCartId.value = cartWithItems.id
|
|
selectedCompanyId.value = selectedCompanyId.value || cartWithItems.companyId
|
|
persist()
|
|
}
|
|
|
|
function hydrate() {
|
|
carts.value = migrateCartItems(loadCarts())
|
|
activeCartId.value = loadActiveCartId()
|
|
if (activeCart.value) {
|
|
syncCartCompanyMeta(activeCart.value)
|
|
selectedCompanyId.value = selectedCompanyId.value || activeCart.value.companyId
|
|
persist()
|
|
}
|
|
}
|
|
|
|
return {
|
|
carts,
|
|
activeCartId,
|
|
selectedCompanyId,
|
|
activeCart,
|
|
totalMeterage,
|
|
itemCount,
|
|
companyNamesLabel,
|
|
maxMeterage,
|
|
remainingCapacity,
|
|
isFull,
|
|
initCartForCompany,
|
|
selectCompany,
|
|
createNewCartForSameCompany,
|
|
addItem,
|
|
updateItemMeterage,
|
|
removeItem,
|
|
setNotes,
|
|
clearActiveCart,
|
|
ensureActiveCartForCartPage,
|
|
hydrate,
|
|
}
|
|
})
|