vkashti-bots / revenue.js
revenue.js
Raw
import dotenv from 'dotenv'
import { Client, GatewayIntentBits } from 'discord.js'
import cron from 'node-cron'

dotenv.config()

const barsyApiUrl = 'https://vkashtibar.barsyonline.com/endpoints/json/'
const botToken = process.env.DISCORD_BOT_TOKEN
const channelId = process.env.DISCORD_BAROVCI_CHANNEL_ID
const barsyUser = process.env.BARSY_USER
const barsyPassword = process.env.BARSY_PASSWORD

const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
})

function getPreviousDay() {
  const date = new Date()
  date.setDate(date.getDate() - 1)
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}

function getToday() {
  const date = new Date()
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}

function formatCurrency(value) {
  return new Intl.NumberFormat('bg-BG', { style: 'currency', currency: 'BGN' }).format(value)
}

async function fetchRelevantCategories() {
  try {
    const response = await fetch(`${barsyApiUrl}Categories_getlist`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Basic ' + btoa(`${barsyUser}:${barsyPassword}`),
      },
      body: JSON.stringify({
        action_type: 'values',
        columns: [
          'cat_id',
          'cat_name',
          'parent_id',
          'cat_path_ids',
          'child_articles_cnt',
          'cat_public_view',
        ],
      }),
    })

    const data = await response.json()

    if (!Array.isArray(data)) {
      console.error('Invalid categories response format:', data)
      return {}
    }

    const relevantCategories = {}
    data.forEach((row) => {
      // Include only top-level categories or categories with articles
      if (
        !row.parent_id || // Top-level category
        row.child_articles_cnt > 0 // Category contains articles
      ) {
        relevantCategories[row.cat_id] = row.cat_name
      }
    })

    console.log('Relevant categories:', relevantCategories)
    return relevantCategories
  } catch (error) {
    console.error('Error fetching relevant categories:', error)
    return {}
  }
}

async function fetchSalesByCategory(catId) {
  try {
    const previousDay = getPreviousDay()
    const response = await fetch(`${barsyApiUrl}Reports_sales_by_articles`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Basic ' + btoa(`${barsyUser}:${barsyPassword}`),
      },
      body: JSON.stringify({
        action_type: 'values',
        filters: {
          cat_id: catId,
          ref_date: previousDay,
        },
        columns: ['oborot', 'total'],
        rows: 200,
      }),
    })

    const data = await response.json()
    const rows = data.rows
    if (!Array.isArray(rows)) {
      console.warn(`No sales data for category ${catId}`)
      return 0
    }

    const totalRevenue = rows.reduce(
      (sum, row) => sum + parseFloat(row.total || 0),
      0
    )
    return totalRevenue
  } catch (error) {
    console.error(`Error fetching sales for category ${catId}:`, error)
    return 0
  }
}

async function fetchSalesGroupedByRelevantCategories() {
  const relevantCategories = await fetchRelevantCategories()

  if (Object.keys(relevantCategories).length === 0) {
    return 'Няма релевантни категории.'
  }

  const categoryRevenue = {}

  for (const [catId, categoryName] of Object.entries(relevantCategories)) {
    const revenue = await fetchSalesByCategory(catId)
    if (revenue > 0) {
      categoryRevenue[categoryName] = revenue
    }
  }

  if (Object.keys(categoryRevenue).length === 0) {
    return 'Няма приходи за релевантните категории.'
  }

  // Format the result
  const sortedCategories = Object.entries(categoryRevenue)
    .sort((a, b) => b[1] - a[1])
    .map(([category, revenue]) => `${category}: ${formatCurrency(revenue)}`)

  const totalRevenue = Object.values(categoryRevenue).reduce(
    (sum, revenue) => sum + revenue,
    0
  )

  return `📊 Продажби по категории:\n${sortedCategories.join(
    '\n'
  )}\n\n💰 Общ приход: ${totalRevenue.toFixed(2)} BGN`
}

// Helper function to send revenue to Discord
async function sendRevenueToDiscord(revenueMessage) {
  const channel = await client.channels.fetch(channelId)

  if (!channel) {
    console.error('Channel not found!')
    return
  }

  await channel.send(revenueMessage)
}

async function fetchSalesByType(paymethodId) {
  try {
    const previousDay = getPreviousDay()
    const today = getToday()
    const response = await fetch(`${barsyApiUrl}Reports_sales_by_accounts`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Basic ' + btoa(`${barsyUser}:${barsyPassword}`),
      },
      body: JSON.stringify({
        action_type: 'values',
        filters: {
          ref_date: [`${previousDay} 01:00:00`, `${today} 00:03:00`],
          paymethod_id: paymethodId, // 1 - cash, 2 - card
        },
        rows: 200,
      }),
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    const data = await response.json()

    const totalOborot = data.rows.reduce((sum, row) => sum + row.total, 0)
    return totalOborot
  } catch (error) {
    console.error('Error fetching sales by type:', error)
    return 0
  }
}

async function fetchCashTotal() {
  const cashTotal = await fetchSalesByType(1)
  return `💵 Общо в брой: ${formatCurrency(cashTotal)}`
}

async function fetchCardTotal() {
  const cardTotal = await fetchSalesByType(2)
  return `💳 Общо с карта: ${formatCurrency(cardTotal)}`
}

async function fetchFullDiscountsByClient() {
  try {
    const previousDay = getPreviousDay()
    const today = getToday()
    const response = await fetch(`${barsyApiUrl}Reports_sales_by_accounts`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Basic ' + btoa(`${barsyUser}:${barsyPassword}`),
      },
      body: JSON.stringify({
        action_type: 'values',
        filters: {
          ref_date: [`${previousDay} 02:59:59`, `${today} 00:03:00`],
        },
        columns: ['oborot', 'client_name', 'discount', 'total', 'discounts', 'user_name'],
        rows: 1000,
      }),
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    const data = await response.json()
    
    // Filter for 100% discounts - looking for discount of -100
    const fullDiscounts = data.rows.filter(row => {
      const discountValue = parseFloat(row.discount || 0)
      return discountValue === -100
    })
    
    // Group by client_name and sum up oborot
    const discountsByClient = {}
    
    // For anonymous clients, track by staff member
    const anonymousDiscountsByStaff = {}
    
    fullDiscounts.forEach(row => {
      const clientName = row.client_name || 'Неизвестен клиент'
      const oborot = parseFloat(row.oborot) || 0
      const staffName = row.user_name || 'Неизвестен персонал'
      
      // Track total by client
      if (!discountsByClient[clientName]) {
        discountsByClient[clientName] = 0
      }
      discountsByClient[clientName] += oborot
      
      // Track anonymous discounts by staff
      if (clientName === 'Анонимен') {
        if (!anonymousDiscountsByStaff[staffName]) {
          anonymousDiscountsByStaff[staffName] = 0
        }
        anonymousDiscountsByStaff[staffName] += oborot
      }
    })
    
    // Format the result
    if (Object.keys(discountsByClient).length === 0) {
      return '🎁 Няма 100% отстъпки'
    }
    
    const sortedClients = Object.entries(discountsByClient)
      .sort((a, b) => b[1] - a[1])
      .map(([client, oborot]) => `${client}: ${formatCurrency(oborot)}`)
    
    const totalDiscount = Object.values(discountsByClient).reduce(
      (sum, oborot) => sum + oborot, 0
    )
    
    let result = `🎁 100% отстъпки по клиенти:\n${sortedClients.join('\n')}\n\nОбщо отстъпки: ${formatCurrency(totalDiscount)}`
    
    // Add breakdown of anonymous discounts by staff if there are any
    if (Object.keys(anonymousDiscountsByStaff).length > 0) {
      const sortedStaff = Object.entries(anonymousDiscountsByStaff)
        .sort((a, b) => b[1] - a[1])
        .map(([staff, oborot]) => `  - ${staff}: ${formatCurrency(oborot)}`)
      
      result += `\n\n📊 Разбивка на отстъпки "Анонимен" по персонал:\n${sortedStaff.join('\n')}`
    }
    
    return result
  } catch (error) {
    console.error('Error fetching full discounts:', error)
    return '🎁 Грешка при извличане на отстъпки'
  }
}

// Schedule the daily task
client.once('ready', () => {
  cron.schedule(
    '30 9 * * *',
    async () => {
      const revenueMessage = await fetchSalesGroupedByRelevantCategories()
      const cashTotalMessage = await fetchCashTotal()
      const cardTotalMessage = await fetchCardTotal()
      const discountsMessage = await fetchFullDiscountsByClient()
      const fullMessage = `${revenueMessage}\n\n${cashTotalMessage}\n${cardTotalMessage}\n\n${discountsMessage}`
      await sendRevenueToDiscord(fullMessage)
    },
    { timezone: 'Europe/Sofia' }
  )
})

// Start the bot
client.login(botToken)