/* ============================================================
   EATME PORTÁL – DAILY SALES SERVICE

   Denní výkaz tržeb + automatický bonus ze směny.

   Výpočty:
   hotovost k odevzdání =
     hotovost na místě + hotovost Bolt + hotovost Foodora

   celková tržba =
     hotovost na místě + kartou na místě +
     celkem Bolt + celkem Wolt + celkem Foodora

   "Přijato kartou" je kontrolní údaj a do tržby se nepřičítá
   podruhé.

   Bonus:
   Ne–Čt: 10 % z částky nad 5 000 Kč
   Pá–So: 10 % z částky nad 10 000 Kč

   Bonus se dělí rovným dílem mezi všechny unikátní zaměstnance
   přihlášené na plánované směny daného dne.
============================================================ */

const SALES_SHEETS = Object.freeze({
  REPORTS: 'DAILY_SALES_REPORTS',
  BONUSES: 'DAILY_SALES_BONUSES'
});


/* ============================================================
   SETUP – SPUSŤ JEDNOU
============================================================ */

function setupDailySales() {

  ensureSalesSheet_(
    SALES_SHEETS.REPORTS,
    [
      'report_id',
      'work_date',
      'location_id',
      'cash_on_site',
      'card_on_site',
      'card_received',
      'cash_bolt',
      'cash_foodora',
      'total_bolt',
      'total_wolt',
      'total_foodora',
      'cash_to_handover',
      'total_revenue',
      'card_tip_gross',
      'card_tip_net',
      'bonus_threshold',
      'bonus_pool',
      'submitted_by',
      'submitted_at',
      'updated_at'
    ]
  );


  ensureSalesSheet_(
    SALES_SHEETS.BONUSES,
    [
      'allocation_id',
      'report_id',
      'work_date',
      'employee_id',
      'bonus_amount',
      'card_tip_amount',
      'created_at',
      'updated_at'
    ]
  );


  Logger.log(
    'Daily sales setup OK'
  );

}



/* ============================================================
   ZAMĚSTNANEC – DNEŠNÍ VÝKAZ
============================================================ */

function getMyDailySalesReport(
  token,
  dateValue
) {

  const user =
    requireUser_(
      token,
      [
        'EMPLOYEE',
        'MANAGER',
        'ADMIN'
      ]
    );


  if (
    !user.employee_id
  ) {

    throw new Error(
      'Účet není propojen se zaměstnancem.'
    );

  }


  const requestedDate =
    normalizeSalesDate_(
      dateValue
    );


  const employeeId =
    String(
      user.employee_id
    );


  /*
   * Nepracujeme slepě s dnešním kalendářním datem.
   * U směn přes půlnoc je provozní datum vždy datum ZAČÁTKU směny.
   *
   * Příklad:
   * pátek 18:00 -> sobota 02:00
   * work_date zůstává pátek.
   *
   * Výkaz je zaměstnanci dostupný od začátku směny
   * až 12 hodin po jejím skončení.
   */
  const resolved =
    resolveEmployeeSalesBusinessDate_(
      employeeId,
      requestedDate
    );


  if (
    !resolved
  ) {

    return {
      scheduled:false,
      work_date:requestedDate
    };

  }


  const date =
    resolved.work_date;


  const context =
    getSalesPlanningContext_(
      date
    );


  const scheduledEmployees =
    context.employee_ids;


  const isScheduled =
    scheduledEmployees.includes(
      employeeId
    );


  if (
    !isScheduled
  ) {

    return {
      scheduled:false,
      work_date:date
    };

  }


  const locationId =
    context.location_id ||
    'LOCATION_MAIN';


  const reports =
    safeSalesRows_(
      SALES_SHEETS.REPORTS
    );


  const report =
    reports.find(
      function(row) {

        return (
          String(
            row.work_date
          ) ===
            date &&

          String(
            row.location_id ||
            ''
          ) ===
            String(
              locationId
            )
        );

      }
    ) ||
    null;


  const values =
    report
      ? salesReportValues_(
          report
        )
      : emptySalesValues_();


  const calculation =
    calculateSalesReport_(
      date,
      values,
      scheduledEmployees.length
    );


  return {

    scheduled:true,

    work_date:
      date,

    location_id:
      locationId,

    report_id:
      report
        ? String(
            report.report_id ||
            ''
          )
        : '',

    values:
      values,

    cash_to_handover:
      calculation.cash_to_handover,

    total_revenue:
      calculation.total_revenue,

    bonus_threshold:
      calculation.bonus_threshold,

    bonus_pool:
      calculation.bonus_pool,

    card_tip_gross:
      calculation.card_tip_gross,

    card_tip_net:
      calculation.card_tip_net,

    employee_count:
      scheduledEmployees.length,

    my_bonus:
      calculation.employee_count > 0
        ? roundMoney_(
            calculation.bonus_pool /
            calculation.employee_count
          )
        : 0,

    my_card_tip:
      calculation.employee_count > 0
        ? roundMoney_(
            calculation.card_tip_net /
            calculation.employee_count
          )
        : 0,

    submitted_at:
      report
        ? String(
            report.submitted_at ||
            ''
          )
        : '',

    employee_ids:
      scheduledEmployees

  };

}



/* ============================================================
   ULOŽENÍ VÝKAZU
============================================================ */

function saveMyDailySalesReport(
  token,
  dateValue,
  payload
) {

  const user =
    requireUser_(
      token,
      [
        'EMPLOYEE',
        'MANAGER',
        'ADMIN'
      ]
    );


  if (
    !user.employee_id
  ) {

    throw new Error(
      'Účet není propojen se zaměstnancem.'
    );

  }


  const date =
    normalizeSalesDate_(
      dateValue
    );


  const context =
    getSalesPlanningContext_(
      date
    );


  const employeeId =
    String(
      user.employee_id
    );


  if (
    !context.employee_ids.includes(
      employeeId
    )
  ) {

    throw new Error(
      'Na tento den nejsi přihlášený na směnu.'
    );

  }


  const values =
    sanitizeSalesPayload_(
      payload
    );


  const calculation =
    calculateSalesReport_(
      date,
      values,
      context.employee_ids.length
    );


  const locationId =
    context.location_id ||
    'LOCATION_MAIN';


  const reports =
    safeSalesRows_(
      SALES_SHEETS.REPORTS
    );


  let report =
    reports.find(
      function(row) {

        return (
          String(
            row.work_date
          ) ===
            date &&

          String(
            row.location_id ||
            ''
          ) ===
            String(
              locationId
            )
        );

      }
    ) ||
    null;


  const now =
    now_();


  if (
    report
  ) {

    updateBy_(
      SALES_SHEETS.REPORTS,
      'report_id',
      report.report_id,
      {

        cash_on_site:
          values.cash_on_site,

        card_on_site:
          values.card_on_site,

        card_received:
          values.card_received,

        cash_bolt:
          values.cash_bolt,

        cash_foodora:
          values.cash_foodora,

        total_bolt:
          values.total_bolt,

        total_wolt:
          values.total_wolt,

        total_foodora:
          values.total_foodora,

        cash_to_handover:
          calculation.cash_to_handover,

        total_revenue:
          calculation.total_revenue,

        card_tip_gross:
          calculation.card_tip_gross,

        card_tip_net:
          calculation.card_tip_net,

        bonus_threshold:
          calculation.bonus_threshold,

        bonus_pool:
          calculation.bonus_pool,

        submitted_by:
          user.user_id,

        submitted_at:
          now,

        updated_at:
          now

      }
    );

  } else {

    report =
      {

        report_id:
          uuid_(
            'SALE'
          ),

        work_date:
          date,

        location_id:
          locationId,

        cash_on_site:
          values.cash_on_site,

        card_on_site:
          values.card_on_site,

        card_received:
          values.card_received,

        cash_bolt:
          values.cash_bolt,

        cash_foodora:
          values.cash_foodora,

        total_bolt:
          values.total_bolt,

        total_wolt:
          values.total_wolt,

        total_foodora:
          values.total_foodora,

        cash_to_handover:
          calculation.cash_to_handover,

        total_revenue:
          calculation.total_revenue,

        card_tip_gross:
          calculation.card_tip_gross,

        card_tip_net:
          calculation.card_tip_net,

        bonus_threshold:
          calculation.bonus_threshold,

        bonus_pool:
          calculation.bonus_pool,

        submitted_by:
          user.user_id,

        submitted_at:
          now,

        updated_at:
          now

      };


    append_(
      SALES_SHEETS.REPORTS,
      report
    );

  }


  replaceSalesBonusAllocations_(
    report.report_id,
    date,
    context.employee_ids,
    calculation.bonus_pool,
    calculation.card_tip_net
  );


  audit_(
    user.user_id,
    'DAILY_SALES_SAVED',
    'DAILY_SALES',
    report.report_id,
    '',
    JSON.stringify({

      date:
        date,

      total_revenue:
        calculation.total_revenue,

      cash_to_handover:
        calculation.cash_to_handover,

      bonus_pool:
        calculation.bonus_pool,

      card_tip_net:
        calculation.card_tip_net,

      employees:
        context.employee_ids

    })
  );


  return getMyDailySalesReport(
    token,
    date
  );

}



/* ============================================================
   ADMIN – HISTORIE TRŽEB
============================================================ */

function getAdminDailySalesReports(
  token,
  dateFrom,
  dateTo
) {

  requireUser_(
    token,
    [
      'ADMIN',
      'MANAGER'
    ]
  );


  const range =
    normalizeSalesRange_(
      dateFrom,
      dateTo
    );


  const reports =
    safeSalesRows_(
      SALES_SHEETS.REPORTS
    )
      .filter(
        function(row) {

          const date =
            String(
              row.work_date ||
              ''
            ).substring(
              0,
              10
            );


          return (
            date >=
              range.from &&
            date <=
              range.to
          );

        }
      )
      .sort(
        function(a,b) {

          return String(
            b.work_date
          ).localeCompare(
            String(
              a.work_date
            )
          );

        }
      );


  const rows =
    reports.map(
      function(row) {

        return {

          report_id:
            String(
              row.report_id ||
              ''
            ),

          work_date:
            String(
              row.work_date ||
              ''
            ).substring(
              0,
              10
            ),

          cash_on_site:
            salesNumber_(
              row.cash_on_site
            ),

          card_on_site:
            salesNumber_(
              row.card_on_site
            ),

          card_received:
            salesNumber_(
              row.card_received
            ),

          cash_bolt:
            salesNumber_(
              row.cash_bolt
            ),

          cash_foodora:
            salesNumber_(
              row.cash_foodora
            ),

          total_bolt:
            salesNumber_(
              row.total_bolt
            ),

          total_wolt:
            salesNumber_(
              row.total_wolt
            ),

          total_foodora:
            salesNumber_(
              row.total_foodora
            ),

          cash_to_handover:
            salesNumber_(
              row.cash_to_handover
            ),

          total_revenue:
            salesNumber_(
              row.total_revenue
            ),

          bonus_pool:
            salesNumber_(
              row.bonus_pool
            ),

          card_tip_gross:
            salesNumber_(
              row.card_tip_gross
            ),

          card_tip_net:
            salesNumber_(
              row.card_tip_net
            ),

          submitted_at:
            String(
              row.submitted_at ||
              ''
            )

        };

      }
    );


  const stats =
    {

      days:
        rows.length,

      total_revenue:
        0,

      cash_to_handover:
        0,

      bonus_pool:
        0,

      card_tip_net:
        0,

      card_on_site:
        0

    };


  rows.forEach(
    function(row) {

      stats.total_revenue +=
        row.total_revenue;

      stats.cash_to_handover +=
        row.cash_to_handover;

      stats.bonus_pool +=
        row.bonus_pool;

      stats.card_tip_net +=
        row.card_tip_net;

      stats.card_on_site +=
        row.card_on_site;

    }
  );


  Object.keys(
    stats
  ).forEach(
    function(key) {

      if (
        typeof stats[
          key
        ] ===
        'number'
      ) {

        stats[
          key
        ] =
          roundMoney_(
            stats[
              key
            ]
          );

      }

    }
  );


  return {

    date_from:
      range.from,

    date_to:
      range.to,

    stats:
      stats,

    rows:
      rows

  };

}



/* ============================================================
   ADMIN / MANAGER – ULOŽIT TRŽBU ZA LIBOVOLNÝ DEN

   Admin nemusí být na směně.
   Do rozdělení bonusu a karetního spropitného vstupují pouze
   zaměstnanci skutečně přihlášení na plánované směny daného dne.
============================================================ */

function saveAdminDailySalesReport(
  token,
  dateValue,
  payload
) {

  const user =
    requireUser_(
      token,
      [
        'ADMIN',
        'MANAGER'
      ]
    );


  const date =
    normalizeSalesDate_(
      dateValue
    );


  const values =
    sanitizeSalesPayload_(
      payload
    );


  const context =
    getSalesPlanningContext_(
      date
    );


  const calculation =
    calculateSalesReport_(
      date,
      values,
      context.employee_ids.length
    );


  const locationId =
    context.location_id ||
    'LOCATION_MAIN';


  const reports =
    safeSalesRows_(
      SALES_SHEETS.REPORTS
    );


  let report =
    reports.find(
      function(row) {

        return (
          String(
            row.work_date ||
            ''
          ).substring(
            0,
            10
          ) ===
            date &&

          String(
            row.location_id ||
            ''
          ) ===
            String(
              locationId
            )
        );

      }
    ) ||
    null;


  const now =
    now_();


  const reportData =
    {

      cash_on_site:
        values.cash_on_site,

      card_on_site:
        values.card_on_site,

      card_received:
        values.card_received,

      cash_bolt:
        values.cash_bolt,

      cash_foodora:
        values.cash_foodora,

      total_bolt:
        values.total_bolt,

      total_wolt:
        values.total_wolt,

      total_foodora:
        values.total_foodora,

      cash_to_handover:
        calculation.cash_to_handover,

      total_revenue:
        calculation.total_revenue,

      card_tip_gross:
        calculation.card_tip_gross,

      card_tip_net:
        calculation.card_tip_net,

      bonus_threshold:
        calculation.bonus_threshold,

      bonus_pool:
        calculation.bonus_pool,

      submitted_by:
        user.user_id,

      submitted_at:
        now,

      updated_at:
        now

    };


  let action =
    'DAILY_SALES_ADMIN_CREATED';


  let oldValue =
    '';


  if (
    report
  ) {

    action =
      'DAILY_SALES_ADMIN_UPDATED';


    oldValue =
      {

        total_revenue:
          salesNumber_(
            report.total_revenue
          ),

        cash_to_handover:
          salesNumber_(
            report.cash_to_handover
          ),

        card_tip_net:
          salesNumber_(
            report.card_tip_net
          ),

        bonus_pool:
          salesNumber_(
            report.bonus_pool
          )

      };


    updateBy_(
      SALES_SHEETS.REPORTS,
      'report_id',
      report.report_id,
      reportData
    );

  } else {

    report =
      {

        report_id:
          uuid_(
            'SALE'
          ),

        work_date:
          date,

        location_id:
          locationId,

        cash_on_site:
          reportData.cash_on_site,

        card_on_site:
          reportData.card_on_site,

        card_received:
          reportData.card_received,

        cash_bolt:
          reportData.cash_bolt,

        cash_foodora:
          reportData.cash_foodora,

        total_bolt:
          reportData.total_bolt,

        total_wolt:
          reportData.total_wolt,

        total_foodora:
          reportData.total_foodora,

        cash_to_handover:
          reportData.cash_to_handover,

        total_revenue:
          reportData.total_revenue,

        card_tip_gross:
          reportData.card_tip_gross,

        card_tip_net:
          reportData.card_tip_net,

        bonus_threshold:
          reportData.bonus_threshold,

        bonus_pool:
          reportData.bonus_pool,

        submitted_by:
          reportData.submitted_by,

        submitted_at:
          reportData.submitted_at,

        updated_at:
          reportData.updated_at

      };


    append_(
      SALES_SHEETS.REPORTS,
      report
    );

  }


  replaceSalesBonusAllocations_(
    report.report_id,
    date,
    context.employee_ids,
    calculation.bonus_pool,
    calculation.card_tip_net
  );


  audit_(
    user.user_id,
    action,
    'DAILY_SALES',
    report.report_id,
    oldValue,
    {

      work_date:
        date,

      total_revenue:
        calculation.total_revenue,

      cash_to_handover:
        calculation.cash_to_handover,

      card_tip_net:
        calculation.card_tip_net,

      bonus_pool:
        calculation.bonus_pool,

      scheduled_employee_ids:
        context.employee_ids

    }
  );


  return {

    ok:
      true,

    report_id:
      report.report_id,

    work_date:
      date,

    employee_count:
      context.employee_ids.length,

    employee_ids:
      context.employee_ids,

    total_revenue:
      calculation.total_revenue,

    cash_to_handover:
      calculation.cash_to_handover,

    card_tip_net:
      calculation.card_tip_net,

    bonus_pool:
      calculation.bonus_pool

  };

}


/* ============================================================
   ADMIN – DETAIL JEDNOHO VÝKAZU TRŽEB
============================================================ */

function getAdminDailySalesReportDetail(
  token,
  reportId
) {

  requireUser_(
    token,
    [
      'ADMIN',
      'MANAGER'
    ]
  );


  const reports =
    safeSalesRows_(
      SALES_SHEETS.REPORTS
    );


  const report =
    reports.find(
      function(row) {

        return (
          String(
            row.report_id ||
            ''
          ) ===
          String(
            reportId ||
            ''
          )
        );

      }
    );


  if (
    !report
  ) {

    throw new Error(
      'Výkaz tržeb nebyl nalezen.'
    );

  }


  return {

    report_id:
      String(
        report.report_id ||
        ''
      ),

    work_date:
      String(
        report.work_date ||
        ''
      ).substring(
        0,
        10
      ),

    location_id:
      String(
        report.location_id ||
        ''
      ),

    values:
      salesReportValues_(
        report
      ),

    cash_to_handover:
      salesNumber_(
        report.cash_to_handover
      ),

    total_revenue:
      salesNumber_(
        report.total_revenue
      ),

    card_tip_gross:
      salesNumber_(
        report.card_tip_gross
      ),

    card_tip_net:
      salesNumber_(
        report.card_tip_net
      ),

    bonus_threshold:
      salesNumber_(
        report.bonus_threshold
      ),

    bonus_pool:
      salesNumber_(
        report.bonus_pool
      ),

    submitted_at:
      String(
        report.submitted_at ||
        ''
      ),

    updated_at:
      String(
        report.updated_at ||
        ''
      )

  };

}



/* ============================================================
   ADMIN – ÚPRAVA VÝKAZU TRŽEB

   Po změně se znovu přepočítá:
   - hotovost k odevzdání
   - celková tržba
   - karetní spropitné po odečtení 21 %
   - bonus z tržeb
   - rozdělení dýšek a bonusů mezi zaměstnance
============================================================ */

function updateAdminDailySalesReport(
  token,
  reportId,
  payload
) {

  const user =
    requireUser_(
      token,
      [
        'ADMIN',
        'MANAGER'
      ]
    );


  const reports =
    safeSalesRows_(
      SALES_SHEETS.REPORTS
    );


  const report =
    reports.find(
      function(row) {

        return (
          String(
            row.report_id ||
            ''
          ) ===
          String(
            reportId ||
            ''
          )
        );

      }
    );


  if (
    !report
  ) {

    throw new Error(
      'Výkaz tržeb nebyl nalezen.'
    );

  }


  const date =
    String(
      report.work_date ||
      ''
    ).substring(
      0,
      10
    );


  const values =
    sanitizeSalesPayload_(
      payload
    );


  const context =
    getSalesPlanningContext_(
      date
    );


  const calculation =
    calculateSalesReport_(
      date,
      values,
      context.employee_ids.length
    );


  const oldValue =
    {

      cash_on_site:
        salesNumber_(
          report.cash_on_site
        ),

      card_on_site:
        salesNumber_(
          report.card_on_site
        ),

      card_received:
        salesNumber_(
          report.card_received
        ),

      cash_bolt:
        salesNumber_(
          report.cash_bolt
        ),

      cash_foodora:
        salesNumber_(
          report.cash_foodora
        ),

      total_bolt:
        salesNumber_(
          report.total_bolt
        ),

      total_wolt:
        salesNumber_(
          report.total_wolt
        ),

      total_foodora:
        salesNumber_(
          report.total_foodora
        ),

      total_revenue:
        salesNumber_(
          report.total_revenue
        ),

      card_tip_net:
        salesNumber_(
          report.card_tip_net
        ),

      bonus_pool:
        salesNumber_(
          report.bonus_pool
        )

    };


  updateBy_(
    SALES_SHEETS.REPORTS,
    'report_id',
    report.report_id,
    {

      cash_on_site:
        values.cash_on_site,

      card_on_site:
        values.card_on_site,

      card_received:
        values.card_received,

      cash_bolt:
        values.cash_bolt,

      cash_foodora:
        values.cash_foodora,

      total_bolt:
        values.total_bolt,

      total_wolt:
        values.total_wolt,

      total_foodora:
        values.total_foodora,

      cash_to_handover:
        calculation.cash_to_handover,

      total_revenue:
        calculation.total_revenue,

      card_tip_gross:
        calculation.card_tip_gross,

      card_tip_net:
        calculation.card_tip_net,

      bonus_threshold:
        calculation.bonus_threshold,

      bonus_pool:
        calculation.bonus_pool,

      updated_at:
        now_()

    }
  );


  replaceSalesBonusAllocations_(
    report.report_id,
    date,
    context.employee_ids,
    calculation.bonus_pool,
    calculation.card_tip_net
  );


  audit_(
    user.user_id,
    'DAILY_SALES_ADMIN_UPDATED',
    'DAILY_SALES',
    report.report_id,
    oldValue,
    {

      cash_on_site:
        values.cash_on_site,

      card_on_site:
        values.card_on_site,

      card_received:
        values.card_received,

      cash_bolt:
        values.cash_bolt,

      cash_foodora:
        values.cash_foodora,

      total_bolt:
        values.total_bolt,

      total_wolt:
        values.total_wolt,

      total_foodora:
        values.total_foodora,

      total_revenue:
        calculation.total_revenue,

      card_tip_net:
        calculation.card_tip_net,

      bonus_pool:
        calculation.bonus_pool

    }
  );


  return getAdminDailySalesReportDetail(
    token,
    report.report_id
  );

}


/* ============================================================
   BONUS PRO TÝDENNÍ MZDU
============================================================ */

function getSalesBonusForEmployeeWeek_(
  employeeId,
  weekStart,
  weekEnd
) {

  try {

    const rows =
      safeSalesRows_(
        SALES_SHEETS.BONUSES
      );


    return roundMoney_(
      rows
        .filter(
          function(row) {

            const date =
              String(
                row.work_date ||
                ''
              );


            return (
              String(
                row.employee_id
              ) ===
                String(
                  employeeId
                ) &&

              date >=
                String(
                  weekStart
                ) &&

              date <=
                String(
                  weekEnd
                )
            );

          }
        )
        .reduce(
          function(sum,row) {

            return (
              sum +
              salesNumber_(
                row.bonus_amount
              )
            );

          },
          0
        )
    );

  }

  catch(error) {

    return 0;

  }

}

function getCardTipForEmployeeWeek_(
  employeeId,
  weekStart,
  weekEnd
) {

  try {

    const rows =
      safeSalesRows_(
        SALES_SHEETS.BONUSES
      );


    return roundMoney_(
      rows
        .filter(
          function(row) {

            const date =
              String(
                row.work_date ||
                ''
              );


            return (
              String(
                row.employee_id
              ) ===
                String(
                  employeeId
                ) &&

              date >=
                String(
                  weekStart
                ) &&

              date <=
                String(
                  weekEnd
                )
            );

          }
        )
        .reduce(
          function(sum,row) {

            return (
              sum +
              salesNumber_(
                row.card_tip_amount
              )
            );

          },
          0
        )
    );

  }

  catch(error) {

    return 0;

  }

}




/* ============================================================
   VÝPOČTY
============================================================ */

function calculateSalesReport_(
  date,
  values,
  employeeCount
) {

  const cashToHandover =
    roundMoney_(
      values.cash_on_site +
      values.cash_bolt +
      values.cash_foodora
    );


  const totalRevenue =
    roundMoney_(
      values.cash_on_site +
      values.card_on_site +
      values.total_bolt +
      values.total_wolt +
      values.total_foodora
    );


  const cardTipGross =
    roundMoney_(
      Math.max(
        0,
        values.card_received -
        values.card_on_site
      )
    );


  /*
   * Ze spropitného placeného kartou se odečte 21 %.
   * Zaměstnancům se tedy rozděluje 79 % rozdílu.
   */
  const cardTipNet =
    roundMoney_(
      cardTipGross *
      0.79
    );


  const day =
    salesDayOfWeek_(
      date
    );


  const isWeekend =
    (
      day ===
        5 ||
      day ===
        6
    );


  const threshold =
    isWeekend
      ? 10000
      : 5000;


  const bonusPool =
    roundMoney_(
      Math.max(
        0,
        totalRevenue -
        threshold
      ) *
      0.10
    );


  return {

    cash_to_handover:
      cashToHandover,

    total_revenue:
      totalRevenue,

    bonus_threshold:
      threshold,

    bonus_pool:
      bonusPool,

    card_tip_gross:
      cardTipGross,

    card_tip_net:
      cardTipNet,

    employee_count:
      Number(
        employeeCount ||
        0
      )

  };

}



/* ============================================================
   PROVOZNÍ DATUM TRŽBY PRO ZAMĚSTNANCE

   Hledáme jeho skutečně přihlášenou plánovanou směnu.
   U směny přes půlnoc patří celý výkaz k datu začátku směny.

   Dostupnost:
   - od začátku směny
   - do 12 hodin po jejím plánovaném konci

   Pokud je více relevantních směn, vybereme tu nejnovější.
============================================================ */

function resolveEmployeeSalesBusinessDate_(
  employeeId,
  requestedDate
) {

  employeeId =
    String(
      employeeId ||
      ''
    );


  if (
    !employeeId
  ) {

    return null;

  }


  const slots =
    displayRows_(
      'SHIFT_SLOTS'
    );


  const signups =
    displayRows_(
      'SHIFT_SIGNUPS'
    );


  const employeeSlotIds =
    {};


  signups.forEach(
    function(signup) {

      if (
        String(
          signup.employee_id ||
          ''
        ) !==
        employeeId
      ) {

        return;

      }


      if (
        String(
          signup.status ||
          ''
        ) !==
        'APPROVED'
      ) {

        return;

      }


      employeeSlotIds[
        String(
          signup.slot_id ||
          ''
        )
      ] =
        true;

    }
  );


  const now =
    new Date();


  /*
   * Pro jistotu pracujeme s requestedDate +/- 1 den,
   * aby frontend mohl stále posílat dnešní datum.
   */
  const requested =
    salesDateOnlyToDate_(
      requestedDate
    );


  const allowedDates =
    {};


  if (
    requested
  ) {

    [
      -1,
      0,
      1
    ].forEach(
      function(offset) {

        const date =
          new Date(
            requested
          );


        date.setDate(
          date.getDate() +
          offset
        );


        allowedDates[
          Utilities.formatDate(
            date,
            CFG.TZ,
            'yyyy-MM-dd'
          )
        ] =
          true;

      }
    );

  }


  const candidates =
    [];


  slots.forEach(
    function(slot) {

      if (
        !employeeSlotIds[
          String(
            slot.slot_id ||
            ''
          )
        ]
      ) {

        return;

      }


      const workDate =
        String(
          slot.date ||
          ''
        ).substring(
          0,
          10
        );


      if (
        !workDate
      ) {

        return;

      }


      if (
        requested &&
        !allowedDates[
          workDate
        ]
      ) {

        return;

      }


      const start =
        salesSlotDateTime_(
          workDate,
          slot.start_time
        );


      let end =
        salesSlotDateTime_(
          workDate,
          slot.end_time
        );


      if (
        !start ||
        !end
      ) {

        return;

      }


      /*
       * Pokud je konec <= začátek, směna přechází přes půlnoc.
       */
      if (
        end.getTime() <=
        start.getTime()
      ) {

        end =
          new Date(
            end.getTime() +
            24 *
            60 *
            60 *
            1000
          );

      }


      const availableFrom =
        start;


      const availableUntil =
        new Date(
          end.getTime() +
          12 *
          60 *
          60 *
          1000
        );


      if (
        now.getTime() <
          availableFrom.getTime() ||
        now.getTime() >
          availableUntil.getTime()
      ) {

        return;

      }


      candidates.push(
        {

          work_date:
            workDate,

          start_at:
            start,

          end_at:
            end,

          slot_id:
            String(
              slot.slot_id ||
              ''
            )

        }
      );

    }
  );


  if (
    !candidates.length
  ) {

    return null;

  }


  candidates.sort(
    function(a,b) {

      return (
        b.start_at.getTime() -
        a.start_at.getTime()
      );

    }
  );


  return candidates[0];

}


function salesDateOnlyToDate_(
  value
) {

  const match =
    String(
      value ||
      ''
    ).match(
      /^(\d{4})-(\d{2})-(\d{2})$/
    );


  if (
    !match
  ) {

    return null;

  }


  return new Date(
    Number(
      match[1]
    ),
    Number(
      match[2]
    ) - 1,
    Number(
      match[3]
    ),
    12,
    0,
    0,
    0
  );

}


function salesSlotDateTime_(
  workDate,
  timeValue
) {

  const dateMatch =
    String(
      workDate ||
      ''
    ).match(
      /^(\d{4})-(\d{2})-(\d{2})$/
    );


  const timeMatch =
    String(
      timeValue ||
      ''
    ).match(
      /(\d{1,2}):(\d{2})/
    );


  if (
    !dateMatch ||
    !timeMatch
  ) {

    return null;

  }


  return new Date(
    Number(
      dateMatch[1]
    ),
    Number(
      dateMatch[2]
    ) - 1,
    Number(
      dateMatch[3]
    ),
    Number(
      timeMatch[1]
    ),
    Number(
      timeMatch[2]
    ),
    0,
    0
  );

}


/* ============================================================
   PLÁNOVANÍ LIDÉ PRO DEN
============================================================ */

function getSalesPlanningContext_(
  date
) {

  const slots =
    displayRows_(
      'SHIFT_SLOTS'
    );


  const signups =
    displayRows_(
      'SHIFT_SIGNUPS'
    );


  const daySlots =
    slots.filter(
      function(slot) {

        return (
          String(
            slot.date ||
            ''
          ).substring(
            0,
            10
          ) ===
          date
        );

      }
    );


  const slotMap =
    {};


  let locationId =
    '';


  daySlots.forEach(
    function(slot) {

      slotMap[
        String(
          slot.slot_id
        )
      ] =
        true;


      if (
        !locationId &&
        slot.location_id
      ) {

        locationId =
          String(
            slot.location_id
          );

      }

    }
  );


  const employeeMap =
    {};


  signups.forEach(
    function(signup) {

      if (
        String(
          signup.status
        ) !==
        'APPROVED'
      ) {

        return;

      }


      if (
        !slotMap[
          String(
            signup.slot_id
          )
        ]
      ) {

        return;

      }


      const employeeId =
        String(
          signup.employee_id ||
          ''
        );


      if (
        employeeId
      ) {

        employeeMap[
          employeeId
        ] =
          true;

      }

    }
  );


  return {

    location_id:
      locationId,

    employee_ids:
      Object.keys(
        employeeMap
      )

  };

}



/* ============================================================
   ROZDĚLENÍ BONUSU
============================================================ */

function replaceSalesBonusAllocations_(
  reportId,
  date,
  employeeIds,
  bonusPool,
  cardTipNet
) {

  const existing =
    safeSalesRows_(
      SALES_SHEETS.BONUSES
    )
      .filter(
        function(row) {

          return (
            String(
              row.report_id
            ) ===
            String(
              reportId
            )
          );

        }
      );


  /*
   * Existující alokace nulujeme.
   * Tím zůstává auditovatelná historie v jednom listu a
   * následně přepíšeme / doplníme správné řádky.
   */

  existing.forEach(
    function(row) {

      updateBy_(
        SALES_SHEETS.BONUSES,
        'allocation_id',
        row.allocation_id,
        {

          bonus_amount:
            0,

          card_tip_amount:
            0,

          updated_at:
            now_()

        }
      );

    }
  );


  if (
    !employeeIds.length
  ) {

    return;

  }


  const share =
    roundMoney_(
      bonusPool /
      employeeIds.length
    );


  const tipShare =
    roundMoney_(
      cardTipNet /
      employeeIds.length
    );


  employeeIds.forEach(
    function(employeeId,index) {

      const old =
        existing.find(
          function(row) {

            return (
              String(
                row.employee_id
              ) ===
              String(
                employeeId
              )
            );

          }
        );


      /*
       * Kvůli haléřovému zaokrouhlení dostane poslední člověk
       * případný rozdíl tak, aby součet přesně seděl.
       */

      const amount =
        index ===
          employeeIds.length -
          1
          ? roundMoney_(
              bonusPool -
              share *
              (
                employeeIds.length -
                1
              )
            )
          : share;


      const tipAmount =
        index ===
          employeeIds.length -
          1
          ? roundMoney_(
              cardTipNet -
              tipShare *
              (
                employeeIds.length -
                1
              )
            )
          : tipShare;


      if (
        old
      ) {

        updateBy_(
          SALES_SHEETS.BONUSES,
          'allocation_id',
          old.allocation_id,
          {

            bonus_amount:
              amount,

            card_tip_amount:
              tipAmount,

            updated_at:
              now_()

          }
        );

      } else {

        append_(
          SALES_SHEETS.BONUSES,
          {

            allocation_id:
              uuid_(
                'SBON'
              ),

            report_id:
              reportId,

            work_date:
              date,

            employee_id:
              employeeId,

            bonus_amount:
              amount,

            card_tip_amount:
              tipAmount,

            created_at:
              now_(),

            updated_at:
              now_()

          }
        );

      }

    }
  );

}



/* ============================================================
   HELPERS
============================================================ */

function emptySalesValues_() {

  return {

    cash_on_site:0,
    card_on_site:0,
    card_received:0,
    cash_bolt:0,
    cash_foodora:0,
    total_bolt:0,
    total_wolt:0,
    total_foodora:0

  };

}


function salesReportValues_(
  row
) {

  return {

    cash_on_site:
      salesNumber_(
        row.cash_on_site
      ),

    card_on_site:
      salesNumber_(
        row.card_on_site
      ),

    card_received:
      salesNumber_(
        row.card_received
      ),

    cash_bolt:
      salesNumber_(
        row.cash_bolt
      ),

    cash_foodora:
      salesNumber_(
        row.cash_foodora
      ),

    total_bolt:
      salesNumber_(
        row.total_bolt
      ),

    total_wolt:
      salesNumber_(
        row.total_wolt
      ),

    total_foodora:
      salesNumber_(
        row.total_foodora
      )

  };

}


function sanitizeSalesPayload_(
  payload
) {

  payload =
    payload ||
    {};


  return {

    cash_on_site:
      salesNumber_(
        payload.cash_on_site
      ),

    card_on_site:
      salesNumber_(
        payload.card_on_site
      ),

    card_received:
      salesNumber_(
        payload.card_received
      ),

    cash_bolt:
      salesNumber_(
        payload.cash_bolt
      ),

    cash_foodora:
      salesNumber_(
        payload.cash_foodora
      ),

    total_bolt:
      salesNumber_(
        payload.total_bolt
      ),

    total_wolt:
      salesNumber_(
        payload.total_wolt
      ),

    total_foodora:
      salesNumber_(
        payload.total_foodora
      )

  };

}


function salesNumber_(
  value
) {

  const number =
    Number(
      String(
        value == null
          ? 0
          : value
      )
        .replace(
          /\s/g,
          ''
        )
        .replace(
          ',',
          '.'
        )
    );


  if (
    !isFinite(
      number
    ) ||
    number <
      0
  ) {

    return 0;

  }


  return number;

}


function roundMoney_(
  value
) {

  return Math.round(
    Number(
      value ||
      0
    ) *
    100
  ) /
  100;

}


function salesDayOfWeek_(
  date
) {

  const parts =
    String(
      date
    ).split(
      '-'
    );


  return new Date(
    Number(
      parts[0]
    ),
    Number(
      parts[1]
    ) -
    1,
    Number(
      parts[2]
    )
  ).getDay();

}


function normalizeSalesRange_(
  dateFrom,
  dateTo
) {

  const today =
    Utilities.formatDate(
      new Date(),
      CFG.TZ,
      'yyyy-MM-dd'
    );


  const now =
    new Date();


  const monthStart =
    Utilities.formatDate(
      new Date(
        now.getFullYear(),
        now.getMonth(),
        1
      ),
      CFG.TZ,
      'yyyy-MM-dd'
    );


  let from =
    String(
      dateFrom ||
      ''
    ).trim();


  let to =
    String(
      dateTo ||
      ''
    ).trim();


  if (
    !/^\d{4}-\d{2}-\d{2}$/.test(
      from
    )
  ) {

    from =
      monthStart;

  }


  if (
    !/^\d{4}-\d{2}-\d{2}$/.test(
      to
    )
  ) {

    to =
      today;

  }


  if (
    from >
    to
  ) {

    const swap =
      from;

    from =
      to;

    to =
      swap;

  }


  return {

    from:
      from,

    to:
      to

  };

}


function normalizeSalesDate_(
  dateValue
) {

  const value =
    String(
      dateValue ||
      ''
    ).trim();


  if (
    /^\d{4}-\d{2}-\d{2}$/.test(
      value
    )
  ) {

    return value;

  }


  return Utilities.formatDate(
    new Date(),
    CFG.TZ,
    'yyyy-MM-dd'
  );

}


function safeSalesRows_(
  sheetName
) {

  try {

    return displayRows_(
      sheetName
    );

  }

  catch(error) {

    return [];

  }

}


/* ============================================================
   DATABÁZOVÝ SPREADSHEET PRO TRŽBY

   Nepoužíváme getPlanningSpreadsheet_().
   Nejprve využijeme existující sh_(), které už celý portál
   používá pro přístup k databázi.

   Díky tomu není potřeba ručně nastavovat SPREADSHEET_ID.
============================================================ */

function getSalesSpreadsheet_() {

  /*
   * 1) Nejspolehlivější varianta:
   *    vezmeme parent spreadsheet existujícího databázového listu.
   */

  try {

    const usersSheet =
      sh_(
        CFG.SHEETS.USERS
      );


    if (
      usersSheet
    ) {

      const spreadsheet =
        usersSheet.getParent();


      if (
        spreadsheet
      ) {

        return spreadsheet;

      }

    }

  }

  catch(error) {

    /*
     * Pokračujeme další možností.
     */

  }


  /*
   * 2) Zkusíme EMPLOYEES.
   */

  try {

    const employeesSheet =
      sh_(
        CFG.SHEETS.EMPLOYEES
      );


    if (
      employeesSheet
    ) {

      const spreadsheet =
        employeesSheet.getParent();


      if (
        spreadsheet
      ) {

        return spreadsheet;

      }

    }

  }

  catch(error) {

    /*
     * Pokračujeme další možností.
     */

  }


  /*
   * 3) Pokud je Apps Script přímo navázaný na tabulku.
   */

  const active =
    SpreadsheetApp
      .getActiveSpreadsheet();


  if (
    active
  ) {

    return active;

  }


  /*
   * 4) Poslední kompatibilní fallback pro standalone projekt.
   */

  const propertyId =
    PropertiesService
      .getScriptProperties()
      .getProperty(
        'SPREADSHEET_ID'
      );


  if (
    propertyId
  ) {

    return SpreadsheetApp
      .openById(
        propertyId
      );

  }


  throw new Error(
    'Nepodařilo se otevřít databázový spreadsheet pro tržby.'
  );

}


function ensureSalesSheet_(
  sheetName,
  headers
) {

  const ss =
    getSalesSpreadsheet_();


  let sheet =
    ss.getSheetByName(
      sheetName
    );


  if (
    !sheet
  ) {

    sheet =
      ss.insertSheet(
        sheetName
      );

  }


  const currentColumns =
    Math.max(
      sheet.getLastColumn(),
      1
    );


  let existing =
    sheet
      .getRange(
        1,
        1,
        1,
        currentColumns
      )
      .getDisplayValues()[0]
      .map(
        function(value) {

          return String(
            value
          ).trim();

        }
      );


  if (
    existing.length ===
      1 &&
    !existing[0]
  ) {

    existing =
      [];

  }


  headers.forEach(
    function(header) {

      if (
        existing.includes(
          header
        )
      ) {

        return;

      }


      const col =
        existing.length +
        1;


      sheet
        .getRange(
          1,
          col
        )
        .setValue(
          header
        );


      existing.push(
        header
      );

    }
  );


  sheet.setFrozenRows(
    1
  );

}
