templates/admin/sales/index.html.twig line 1

Open in your IDE?
  1. {% extends 'base.html.twig' %}
  2. {% block title %}
  3.     {% trans %}
  4.         sales
  5.     {% endtrans %}
  6. {% endblock %}
  7. {% block head %}
  8.     <style>
  9.         /* Keep all form elements at 12px on this page */
  10.         select, textarea, button { font-size: 12px !important; }
  11.         .form-control, .custom-select { font-size: 12px !important; }
  12.         /* Select2 text sizes */
  13.         .select2-container .select2-selection__rendered,
  14.         .select2-results__option,
  15.         .select2-container .select2-search__field { font-size: 12px !important; }
  16.         #adder-allocated-quantity,
  17.         #adder-quantity-unit {
  18.             vertical-align: middle;
  19.         }
  20.         .product-select-cell {
  21.             max-width: 250px;
  22.             /* Hücrenin alabileceği maksimum genişlik. İstediğiniz gibi ayarlayın. */
  23.             white-space: nowrap;
  24.             overflow: hidden;
  25.             text-overflow: ellipsis;
  26.         }
  27.         /* Ekleme Satırı Stilleri */
  28.         #adder-row {
  29.             background-color: #f8f9fa;
  30.             /* Hafif gri arkaplan */
  31.         }
  32.         #adder-row td {
  33.             vertical-align: middle;
  34.             padding: 0.25rem; /* Boşluk azaltıldı */
  35.             border-top: 2px dashed #e3e6f0;
  36.         }
  37.         /* Adder satırında hücreler arası yatay boşluğu minimuma indir */
  38.         #adder-row .input-group { margin-right: 4px; }
  39.         #adder-row .btn-icon, #adder-row .btn { margin-right: 4px; }
  40.         /* Yeni Card Header Stili */
  41.         .bg-light-blue {
  42.             background-color: #f0f3ff !important;
  43.             /* Hafif mavi arkaplan */
  44.         }
  45.         #product-list-table-tbody input {
  46.             /*border: none;*/
  47.             /*padding: 0;*/
  48.             /*margin: 0;*/
  49.             background-color: white;
  50.         }
  51.         .info-span {
  52.             color: red;
  53.             font-weight: bold;
  54.         }
  55.         .cost-detail-button {
  56.             color: blue;
  57.             font-width: bold;
  58.             border: none;
  59.             background-color: transparent;
  60.         }
  61.         .attention-icon {
  62.             display: inline-block;
  63.             vertical-align: middle;
  64.             margin-left: 5px;
  65.             font-size: 16px;
  66.             color: red;
  67.             font-weight: bold;
  68.         }
  69.         .quantity-input-added {
  70.             width: 60px;
  71.         }
  72.         .select2-container {
  73.             width: 100% !important;
  74.             /* Genişlik uyumluluğu için */
  75.         }
  76.         /* En kritik CSS düzeltmesi olmasa da, z-index çakışmalarına karşı bir güvencedir.
  77.            Asıl çözüm JavaScript'tedir. */
  78.         .select2-container--open {
  79.             z-index: 1056 !important;
  80.         }
  81.         .select2-container {
  82.             width: 100% !important;
  83.         }
  84.         .select2-container--open {
  85.             z-index: 1056 !important;
  86.         }
  87.         .select2-container--bootstrap4 .select2-selection--single
  88.         {
  89.             padding: .50rem !important;
  90.         }
  91.         .select2-container .select2-selection--single {
  92.             height: 28px !important;
  93.         }
  94.         /* === SEÇİM KARTLARI İÇİN CSS === */
  95.         .selection-card-label {
  96.             display: block;
  97.             cursor: pointer;
  98.             width: 100%;
  99.         }
  100.         .selection-card {
  101.             border: 2px solid #e3e6f0;
  102.             border-radius: 0.5rem;
  103.             padding: 0.50rem;
  104.             transition: all 0.2s ease-in-out;
  105.             display: flex;
  106.             justify-content: center;
  107.             align-items: center;
  108.             width: 100%;
  109.         }
  110.         /* Kartın üzerine gelindiğinde (hover) */
  111.         .selection-card:hover {
  112.             border-color: #4e73df;
  113.             transform: translateY(-2px);
  114.             /* Hafif yukarı kalkma efekti */
  115.             box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
  116.         }
  117.         /* Kart içindeki başlık (Fatura, Proforma) */
  118.         .selection-title {
  119.             font-size: 1.1rem;
  120.             font-weight: 500;
  121.             color: #5a5c69;
  122.             transition: color 0.2s ease-in-out;
  123.         }
  124.         /* Onay işareti (başlangıçta gizli) */
  125.         .checkmark {
  126.             font-size: 1.25rem;
  127.             color: #4e73df;
  128.             opacity: 0;
  129.             transform: scale(0.5);
  130.             transition: all 0.2s ease-out;
  131.             margin-left: 0.75rem;
  132.         }
  133.         /* SEÇİM YAPILDIĞINDAKİ STİLLER */
  134.         /* CSS :has() seçicisi ile, içindeki radio input seçili olan etiketin kartını stillendiriyoruz. */
  135.         .selection-card-label:has(input[type="radio"]:checked) .selection-card {
  136.             border-color: #4e73df;
  137.             background-color: #f0f3ff;
  138.         }
  139.         /* Seçili kartın başlık rengini değiştir */
  140.         .selection-card-label:has(input[type="radio"]:checked) .selection-title {
  141.             color: #4e73df;
  142.             font-weight: 600;
  143.         }
  144.         /* Seçili kartın onay işaretini görünür yap */
  145.         .selection-card-label:has(input[type="radio"]:checked) .checkmark {
  146.             opacity: 1;
  147.             transform: scale(1);
  148.         }
  149.         /* Bilgi satırı için stil */
  150.         #info-details-content {
  151.             background-color: #f1faff;
  152.             /* Hafif mavi arka plan */
  153.             border-bottom: 2px dashed #e3e6f0;
  154.             /* Altına kesikli çizgi ekler */
  155.         }
  156.         .info-details-content-class {
  157.             background-color: #f1faff;
  158.             /* Hafif mavi arka plan */
  159.             border-bottom: 2px dashed #e3e6f0;
  160.             /* Altına kesikli çizgi ekler */
  161.         }
  162.         /* Make product table full-width and remove right gaps */
  163.         .card-body > .table-responsive { padding-right: 0; }
  164.         #product-list-table { width: 100% !important; table-layout: fixed; }
  165.         #product-list-table th, #product-list-table td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  166.         #product-list-table .product-select-cell select { width: 100%; }
  167.         /* Adjust specific column widths to fit viewport */
  168.         .summary-row td {
  169.             border-top: none;
  170.             background-color: #f8f9fc;
  171.             padding: 0;
  172.         }
  173.         .sale-summary-row {
  174.             display: flex;
  175.             justify-content: flex-end;
  176.             align-items: center;
  177.             gap: 12px;
  178.             padding: 0.35rem 0.75rem;
  179.             border-radius: 0.35rem;
  180.             margin-right: 1.5rem;
  181.         }
  182.         .sale-summary-row .summary-label {
  183.             font-weight: 600;
  184.             color: #4b5563;
  185.             white-space: nowrap;
  186.         }
  187.         .sale-summary-row .summary-value {
  188.             font-weight: 700;
  189.             color: #111827;
  190.             text-align: right;
  191.             min-width: 120px;
  192.             white-space: nowrap;
  193.         }
  194.         .sale-summary-row.summary-tax .summary-label,
  195.         .sale-summary-row.summary-tax .summary-value {
  196.             display: flex;
  197.             flex-direction: column;
  198.             align-items: flex-end;
  199.             gap: 4px;
  200.         }
  201.         .sale-summary-row.summary-tax .summary-label div,
  202.         .sale-summary-row.summary-tax .summary-value div {
  203.             white-space: nowrap;
  204.         }
  205.         .sale-summary-row.summary-total .summary-value {
  206.             font-size: 1.25rem;
  207.         }
  208.         #product-list-table th:nth-child(1), #product-list-table td:nth-child(1) { width: 5%; }
  209.         #product-list-table th:nth-child(2), #product-list-table td:nth-child(2) { width: 25%; }
  210.         #product-list-table th:nth-child(3), #product-list-table td:nth-child(3) { width: 10%; }
  211.         #product-list-table th:nth-child(4), #product-list-table td:nth-child(4) { width: 10%; }
  212.         #product-list-table th:nth-child(5), #product-list-table td:nth-child(5) { width: 10%; }
  213.         #product-list-table th:nth-child(6), #product-list-table td:nth-child(6) { width: 8%; }
  214.         #product-list-table th:nth-child(7), #product-list-table td:nth-child(7) { width: 8%; }
  215.         #product-list-table th:nth-child(8), #product-list-table td:nth-child(8) { width: 15%; }
  216.         #product-list-table th:nth-child(9), #product-list-table td:nth-child(9) { width: 9%; }
  217.     </style>
  218. {% endblock %}
  219. {% block body %}
  220.     <div class="container-fluid m-0 p-0">
  221.         <div class="card shadow mb-4">
  222.             <div class="card-header py-3 d-flex justify-content-between align-items-center">
  223.                 <div class="btn-group" role="group">
  224.                     <div class="d-md-none">
  225.                         <div class="dropdown">
  226.                             <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  227.                                 {% trans %} actions {% endtrans %}
  228.                             </button>
  229.                             <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
  230.                                 <button type="button" class="dropdown-item" data-toggle="modal" data-target="#payments-modal">
  231.                                     <i class="fas fa-money-check-alt mr-1"></i>
  232.                                     {% trans %}payments{% endtrans %}
  233.                                 </button>
  234.                                 <button type="button" data-toggle="modal" data-target="#add-customer-modal" class="dropdown-item">
  235.                                     <i class="fas fa-user-plus mr-1"></i>
  236.                                     {% trans %}addCustomer{% endtrans %}
  237.                                 </button>
  238.                                 <button type="button" class="dropdown-item js-edit-customer">
  239.                                     <i class="fas fa-user-edit mr-1"></i>
  240.                                     {% trans %}editCustomer{% endtrans %}
  241.                                 </button>
  242.                                 <button type="button" class="dropdown-item" id="btn-gift-voucher-mobile">
  243.                                     <i class="fas fa-gift mr-1"></i>
  244.                                     {{ 'giftVoucher'|trans }}
  245.                                 </button>
  246.                             </div>
  247.                         </div>
  248.                     </div>
  249.                     <div class="d-none d-md-block">
  250.                         <div class="btn-group" role="group">
  251.                             <button type="button" class="btn btn-outline-secondary" data-toggle="modal" data-target="#payments-modal">
  252.                                 <i class="fas fa-money-check-alt mr-1"></i>
  253.                                 {% trans %}payments{% endtrans %}
  254.                             </button>
  255.                             <button type="button" data-toggle="modal" data-target="#add-customer-modal" class="btn btn-outline-secondary">
  256.                                 <i class="fas fa-user-plus mr-1"></i>
  257.                                 {% trans %}addCustomer{% endtrans %}
  258.                             </button>
  259.                             <button type="button" class="btn btn-outline-secondary js-edit-customer">
  260.                                 {% trans %}editCustomer{% endtrans %}
  261.                             </button>
  262.                             <button type="button" class="btn btn-outline-secondary" id="btn-gift-voucher" title="{{ 'useGiftVoucher'|trans }}">
  263.                                 <i class="fas fa-gift mr-1"></i>
  264.                                 {{ 'giftVoucher'|trans }}
  265.                             </button>
  266.                         </div>
  267.                     </div>
  268.                 </div>
  269.                 <button type="button" id="submitbtn" class="btn btn-success">
  270.                     <i class="fas fa-save mr-1"></i>
  271.                     {% trans %}
  272.                         save
  273.                     {% endtrans %}
  274.                 </button>
  275.             </div>
  276.             <div class="card-body">
  277.                 {# FATURA VE PROFORMA SEÇENEKLERİ #}
  278.                 <div class="form-group">
  279.                     <label class="form-label font-weight-bold">{{ 'salesType'|trans }}</label>
  280.                     <div class="row">
  281.                         {# Fatura Seçeneği #}
  282.                         <div class="col-md-6">
  283.                             <label class="selection-card-label">
  284.                                 <input type="radio" name="salesType" class="d-none" value="invoice">
  285.                                 <div class="selection-card">
  286.                                     <span class="selection-title">{% trans %}invoice{% endtrans %}</span>
  287.                                     <i class="fas fa-check-circle checkmark"></i>
  288.                                 </div>
  289.                             </label>
  290.                         </div>
  291.                         {# Proforma Seçeneği #}
  292.                         <div class="col-md-6">
  293.                             <label class="selection-card-label">
  294.                                 <input type="radio" name="salesType" class="d-none" value="proforma" checked>
  295.                                 <div class="selection-card">
  296.                                     <span class="selection-title">{% trans %}proforma{% endtrans %}</span>
  297.                                     <i class="fas fa-check-circle checkmark"></i>
  298.                                 </div>
  299.                             </label>
  300.                         </div>
  301.                     </div>
  302.                 </div>
  303.                 <div style="margin-bottom: 10px">
  304.                     {# SATIŞ FORMU başlangıç #}
  305.                     {{ form_start(form) }}
  306.                     <div class="">
  307.                         <div class="row">
  308.                             <div class="col-lg-6 col-md-12">
  309.                                 {{ form_row(form.totalPurchasePrice) }}
  310.                                 {{ form_row(form.status) }}
  311.                             </div>
  312.                             <div class="col-lg-6 col-md-12">
  313.                                 {{ form_row(form.customer) }}
  314.                                 {{ form_row(form.seller) }}
  315.                             </div>
  316.                         </div>
  317.                         <div class="row">
  318.                             <div class="col-lg-3 col-md-6">{{ form_row(form.salesDate) }}</div>
  319.                             <div class="col-lg-3 col-md-6">{{ form_row(form.deliveryDate) }}</div>
  320.                             <div class="col-lg-3 col-md-6">{{ form_row(form.dueDate) }}</div>
  321.                             <div class="col-lg-3 col-md-6">{{ form_row(form.validDate) }}</div>
  322.                         </div>
  323.                     </div>
  324.                     {# SATIŞ FORMU bitiş #}
  325.                     {# Ödemeler Tablosu modal #}
  326.                     <div class="modal fade" id="payments-modal" role="dialog" aria-labelledby="paymentsModalLabel" aria-hidden="true">
  327.                         <div class="modal-dialog modal-xl" role="document">
  328.                             <div class="modal-content">
  329.                                 <div class="modal-header">
  330.                                     <h5 class="modal-title" id="paymentsModalLabel">{% trans %}payments{% endtrans %}</h5>
  331.                                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  332.                                         <span aria-hidden="true">&times;</span>
  333.                                     </button>
  334.                                 </div>
  335.                                 <div class="modal-body">
  336.                                     <div class="card mb-1">
  337.                                         <div class="card-header d-flex align-items-center justify-content-between">
  338.                                             <div>
  339.                                                 <h5 class="mb-0">{% trans %}payments{% endtrans %}</h5>
  340.                                                 <div style="float: left; width: 250px">
  341.                                                     {% trans %}total{% endtrans %}:
  342.                                                     <span id="total-amount-span"></span>
  343.                                                     {% trans %}remainingAmount{% endtrans %}:
  344.                                                     <span id="remainder-amount-span"></span>
  345.                                                 </div>
  346.                                             </div>
  347.                                             <button class="btn btn-primary" type="button" id="btn-addPayment" data-toggle="modal" data-target="#add-payment-modal">
  348.                                                 {% trans %}addPayment{% endtrans %}
  349.                                             </button>
  350.                                         </div>
  351.                                         <div class="card-body">
  352.                                             <div>
  353.                                                 <table class="table" id="payment-list-table">
  354.                                                     <thead>
  355.                                                     <tr>
  356.                                                         <th>{% trans %}amount{% endtrans %}</th>
  357.                                                         <th>{% trans %}status{% endtrans %}</th>
  358.                                                         <th>{% trans %}paymentDate{% endtrans %}</th>
  359.                                                         <th>{% trans %}dueDate{% endtrans %}</th>
  360.                                                         <th>{% trans %}paymentMethod{% endtrans %}</th>
  361.                                                         <th>{% trans %}description{% endtrans %}</th>
  362.                                                         <th>&nbsp;</th>
  363.                                                     </tr>
  364.                                                     </thead>
  365.                                                     <tbody id="payment-list-table-tbody"></tbody>
  366.                                                 </table>
  367.                                             </div>
  368.                                         </div>
  369.                                     </div>
  370.                                 </div>
  371.                             </div>
  372.                         </div>
  373.                     </div>
  374.                     <div class="card border-0 shadow-sm">
  375.                         <div class="card-header bg-light-blue border-0 py-3">
  376.                             <div class="row align-items-center">
  377.                                 <div class="col-md-4">
  378.                                     <label for="warehouse-select" class="font-weight-bold mb-1">{% trans %}warehouse{% endtrans %}</label>
  379.                                     <select id="warehouseselect" class="form-control">
  380.                                         <option value="">{% trans %}selectwarehouse{% endtrans %}</option>
  381.                                         {% for warehouse in warehouses %}
  382.                                             <option value="{{ warehouse.id }}">{{ warehouse.name }}</option>
  383.                                         {% endfor %}
  384.                                     </select>
  385.                                 </div>
  386.                                 <div class="col-md-5"></div>
  387.                                 <div id="product-info-box" class="col-md-3" style="display: none;">
  388.                                     <div class="info-box-content">
  389.                                         <div>
  390.                                             {% trans %}stockQuantity{% endtrans %}:
  391.                                             <strong id="info-stock"></strong>
  392.                                         </div>
  393.                                         <div>
  394.                                             {% trans %}averageCost{% endtrans %}:
  395.                                             <strong id="info-cost"></strong>
  396.                                         </div>
  397.                                     </div>
  398.                                 </div>
  399.                             </div>
  400.                         </div>
  401.                         <div class="card-body" style="padding: 0">
  402.                             <div class="table-responsive">
  403.                                 <table class="table modern-table" id="product-list-table" style="width: 100%;">
  404.                                     <thead>
  405.                                     <tr>
  406.                                         <th style="width: 5%;">{% trans %}info{% endtrans %}</th>
  407.                                         <th style="width: 20%;">{% trans %}product{% endtrans %}</th>
  408.                                         <th style="width: 20%;">{% trans %}quantity{% endtrans %}</th>
  409.                                         <th style="width: 15%;">{% trans %}unitPrice{% endtrans %}</th>
  410.                                         <th style="width: 10%;">{% trans %}discount{% endtrans %} (%)</th>
  411.                                         <th style="width: 10%;">TVA (%)</th>
  412.                                         <th style="width: 15%; text-align: left !important;" class="text-right">{% trans %}total{% endtrans %}</th>
  413.                                         <th style="width: 15%;" class="text-center">{% trans %}action{% endtrans %}</th>
  414.                                     </tr>
  415.                                     </thead>
  416.                                     <tbody id="product-list-table-tbody">
  417.                                     </tbody>
  418.                                     <tbody id="adder-body">
  419.                                     <tr id="adder-row">
  420.                                         <td>
  421.                                             <button type="button" id="show-info-btn"
  422.                                                     class="btn btn-sm btn-info btn-icon" title="{{ 'productInfo'|trans }}">
  423.                                                 <i class="fas fa-info-circle"></i>
  424.                                             </button>
  425.                                         </td>
  426.                                         <td class="product-select-cell">
  427.                                             <select id="productselect" class="form form-control">
  428.                                                 <option value="">{{ 'selectWarehouseFirst'|trans }}</option>
  429.                                             </select>
  430.                                         </td>
  431.                                         <td>
  432.                                             <div class="input-group input-group-sm" style="max-width: 220px;">
  433.                                                 <input type="text" id="adder-allocated-quantity" class="form-control" value="0" min="0">
  434.                                                 <div class="input-group-append">
  435.                                                     <select id="adder-quantity-unit-select" class="custom-select custom-select-sm">
  436.                                                         <option value="">-</option>
  437.                                                     </select>
  438.                                                 </div>
  439.                                             </div>
  440.                                         </td>
  441.                                         <td><input type="text" id="adder-price" value="0" min="0"
  442.                                                    class="form-control form-control-sm price-mask" placeholder="Fiyat">
  443.                                         </td>
  444.                                         <td>
  445.                                             <select name="adder-discount-select" id="adder-discount-select"
  446.                                                     class="form-control form-control-sm">
  447.                                                 <option value="0">% 0</option>
  448.                                                 <option value="1">% 1</option>
  449.                                                 <option value="2">% 2</option>
  450.                                                 <option value="3">% 3</option>
  451.                                                 <option value="4">% 4</option>
  452.                                                 <option value="5">% 5</option>
  453.                                                 <option value="20">% 20</option>
  454.                                                 <option value="30">% 30</option>
  455.                                                 <option value="100">% 100</option>
  456.                                             </select>
  457.                                         </td>
  458.                                         <td>
  459.                                             <select name="adder-tva-select" id="adder-tva-select"
  460.                                                     class="form-control form-control-sm">
  461.                                                 <option value="0" disabled>% 0</option>
  462.                                                 <option value="10">% 10</option>
  463.                                                 <option value="20">% 20</option>
  464.                                             </select>
  465.                                         </td>
  466.                                         <td class="text-right font-weight-bold small">
  467.                                             <input name="adder-total" id="adder-total" type="number"
  468.                                                    class="form-control form-control-sm"
  469.                                                    placeholder="{% trans %} total {% endtrans %}"/>
  470.                                         </td>
  471.                                         <td class="text-center">
  472.                                             <div class="btn-group" role="group">
  473.                                                 <button type="button" id="open-unallocated-modal" class="btn btn-outline-secondary btn-sm" title="{% trans %}unAllocatedQuantity{% endtrans %}">
  474.                                                     <i class="fas fa-box-open"></i>
  475.                                                     <span id="unalloc-display" class="badge badge-light ml-1">0</span>
  476.                                                 </button>
  477.                                                 <button type="button" id="add-product-row-btn"
  478.                                                         class="btn btn-sm btn-success btn-icon">
  479.                                                     <i class="fas fa-plus"></i>
  480.                                                 </button>
  481.                                             </div>
  482.                                             <input type="number" id="adder-unallocated-quantity" class="form-control d-none" value="0" min="0">
  483.                                             <select id="adder-unallocated-quantity-unit-select" class="custom-select custom-select-sm d-none">
  484.                                                 <option value="">-</option>
  485.                                             </select>
  486.                                         </td>
  487.                                     </tr>
  488.                                     <tr id="info-details-row">
  489.                                         <td colspan="8" style="padding: 0; border-top: none;">
  490.                                             <div id="info-details-content" class="p-3" style="display: none;">
  491.                                             </div>
  492.                                         </td>
  493.                                     </tr>
  494.                                     </tbody>
  495.                                     <tfoot>
  496.                                         <!-- Rows will be injected by updateTotalAmounts() -->
  497.                                         <tr class="summary-row-placeholder">
  498.                                             <td colspan="8"></td>
  499.                                         </tr>
  500.                                     </tfoot>
  501.                                 </table>
  502.                             </div>
  503.                         </div>
  504.                     </div>
  505.                     {{ form_end(form) }}
  506.                 </div>
  507.             </div>
  508.         </div>
  509.     </div>
  510.     <div class="modal fade" id="add-customer-modal" role="dialog" aria-labelledby="customerModalLabel" aria-hidden="true">
  511.         <div class="modal-dialog" role="document">
  512.             <div class="modal-content">
  513.                 <div class="modal-header">
  514.                     <h5 class="modal-title" id="customerModalLabel">{% trans %}addCustomer{% endtrans %}</h5>
  515.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  516.                         <span aria-hidden="true">&times;</span>
  517.                     </button>
  518.                 </div>
  519.                 {{ form_start(customerForm) }}
  520.                 <div class="modal-body">
  521.                     {{ form_widget(customerForm) }}
  522.                 </div>
  523.                 <div class="modal-footer">
  524.                     <button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans %}cancel{% endtrans %}</button>
  525.                     <button type="submit" id="btn-customer-save" class="btn btn-primary">{% trans %}save{% endtrans %}</button>
  526.                 </div>
  527.                 {{ form_end(customerForm) }}
  528.             </div>
  529.         </div>
  530.     </div>
  531.     <div class="modal fade" id="unallocatedModal" tabindex="-1" role="dialog" aria-labelledby="unallocatedModalLabel" aria-hidden="true">
  532.         <div class="modal-dialog" role="document">
  533.             <div class="modal-content">
  534.                 <div class="modal-header">
  535.                     <h5 class="modal-title" id="unallocatedModalLabel">{% trans %}unAllocatedQuantity{% endtrans %}</h5>
  536.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  537.                         <span aria-hidden="true">&times;</span>
  538.                     </button>
  539.                 </div>
  540.                 <div class="modal-body">
  541.                     <div class="form-group">
  542.                         <label for="modal-unallocated-quantity">{% trans %}quantity{% endtrans %}</label>
  543.                         <input type="text" id="modal-unallocated-quantity" class="form-control mask-money" value="0">
  544.                     </div>
  545.                     <div class="form-group">
  546.                         <label for="modal-unallocated-unit">{% trans %}unit{% endtrans %}</label>
  547.                         <select id="modal-unallocated-unit" class="custom-select">
  548.                             <option value="">-</option>
  549.                         </select>
  550.                     </div>
  551.                 </div>
  552.                 <div class="modal-footer">
  553.                     <button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans %}close{% endtrans %}</button>
  554.                     <button type="button" id="save-unallocated-btn" class="btn btn-primary">{% trans %}save{% endtrans %}</button>
  555.                 </div>
  556.             </div>
  557.         </div>
  558.     </div>
  559.     <div class="modal fade" id="add-payment-modal" role="dialog" aria-labelledby="paymentModalLabel" aria-hidden="true">
  560.         <div class="modal-dialog" role="document">
  561.             <div class="modal-content">
  562.                 <div class="modal-header">
  563.                     <h5 class="modal-title" id="paymentModalLabel">{% trans %}addPayment{% endtrans %}</h5>
  564.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  565.                         <span aria-hidden="true">&times;</span>
  566.                     </button>
  567.                 </div>
  568.                 <div class="modal-body">
  569.                     {{ form_widget(paymentForm) }}
  570.                     {{ form_start(paymentForm) }}
  571.                     {{ form_end(paymentForm) }}
  572.                 </div>
  573.                 <div class="modal-footer">
  574.                     <button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans %}cancel{% endtrans %}</button>
  575.                     <button type="button" id="save-payment-btn" class="btn btn-primary">{% trans %}save{% endtrans %}</button>
  576.                 </div>
  577.             </div>
  578.         </div>
  579.     </div>
  580.     <div class="modal fade" id="updateCustomerModal" tabindex="-1" role="dialog" aria-labelledby="updateModalLabel" aria-hidden="true">
  581.         <div class="modal-dialog" role="document">
  582.             <div class="modal-content">
  583.                 <div class="modal-header">
  584.                     <h5 class="modal-title" id="updateModalLabel">{% trans %}updateCustomerInfoMessage{% endtrans %}</h5>
  585.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  586.                         <span aria-hidden="true">&times;</span>
  587.                     </button>
  588.                 </div>
  589.                 <form id="updateCustomerForm">
  590.                     <div class="modal-body">
  591.                         <input type="hidden" id="update_customer_id">
  592.                         <div class="form-group">
  593.                             <label for="update_customer_fullName">{% trans %}fullName{% endtrans %}</label>
  594.                             <input type="text" class="form-control" id="update_customer_fullName" required>
  595.                         </div>
  596.                         <div class="form-group">
  597.                             <label for="update_customer_email">{% trans %}email{% endtrans %}</label>
  598.                             <input type="email" class="form-control" id="update_customer_email">
  599.                         </div>
  600.                         <div class="form-group">
  601.                             <label for="update_customer_phone">{% trans %}phone{% endtrans %}</label>
  602.                             <input type="text" class="form-control" id="update_customer_phone">
  603.                         </div>
  604.                         <div class="form-group">
  605.                             <label for="update_customer_address">{% trans %}address{% endtrans %}</label>
  606.                             <textarea class="form-control" id="update_customer_address" rows="3"></textarea>
  607.                         </div>
  608.                     </div>
  609.                     <div class="modal-footer">
  610.                         <button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans %}close{% endtrans %}</button>
  611.                         <button type="submit" class="btn btn-primary">{% trans %}save{% endtrans %}</button>
  612.                     </div>
  613.                 </form>
  614.             </div>
  615.         </div>
  616.     </div>
  617.     <div class="modal fade" id="updateServiceModal" tabindex="-1" role="dialog" aria-labelledby="updateServiceModalLabel" aria-hidden="true">
  618.         <div class="modal-dialog" role="document">
  619.             <div class="modal-content">
  620.                 <div class="modal-header">
  621.                     <h5 class="modal-title" id="updateServiceModalLabel">{% trans %}updateServiceInfoMessage{% endtrans %}</h5>
  622.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  623.                         <span aria-hidden="true">&times;</span>
  624.                     </button>
  625.                 </div>
  626.                 <form id="updateServiceForm" onsubmit="return false;">
  627.                     <div class="modal-body">
  628.                         <input type="hidden" id="update_service_id">
  629.                         <div class="form-group">
  630.                             <label for="update_service_name">{% trans %}serviceName{% endtrans %}</label>
  631.                             <input type="text" class="form-control" id="update_service_name">
  632.                         </div>
  633.                         <div class="form-group">
  634.                             <label for="update_service_cost">{% trans %}cost{% endtrans %}</label>
  635.                             <input type="text" value="0.00" class="form-control mask-money" id="update_service_cost" required>
  636.                         </div>
  637.                         <div class="form-group">
  638.                             <label for="update_service_description">{% trans %}description{% endtrans %}</label>
  639.                             <input type="text" class="form-control" id="update_service_description">
  640.                         </div>
  641.                     </div>
  642.                     <div class="modal-footer">
  643.                         <button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans %}close{% endtrans %}</button>
  644.                         <button type="button" class="btn btn-primary" id="service_modal_save_changes_btn">{% trans %}save{% endtrans %}</button>
  645.                     </div>
  646.                 </form>
  647.             </div>
  648.         </div>
  649.     </div>
  650.     <div class="modal fade" id="autoPaymentModal" tabindex="-1" role="dialog" aria-labelledby="autoPaymentModalLabel" aria-hidden="true">
  651.         <div class="modal-dialog modal-dialog-centered" role="document">
  652.             <div class="modal-content">
  653.                 <div class="modal-header">
  654.                     <h5 class="modal-title" id="autoPaymentModalLabel">{{ 'autoPaymentConfirmation'|trans }}</h5>
  655.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  656.                         <span aria-hidden="true">&times;</span>
  657.                     </button>
  658.                 </div>
  659.                 <div class="modal-body">
  660.                     <p id="auto-payment-message" class="mb-3"></p>
  661.                     <div class="border rounded px-3 py-2 bg-light">
  662.                         <div class="d-flex justify-content-between"><span>{{ 'salesTotal'|trans }}</span><span id="auto-payment-sale-total">0,00 {{ 'currency.symbol'|trans }}</span></div>
  663.                         <div class="d-flex justify-content-between"><span>{{ 'currentPayments'|trans }}</span><span id="auto-payment-current-payments">0,00 {{ 'currency.symbol'|trans }}</span></div>
  664.                         <div class="d-flex justify-content-between font-weight-bold"><span>{{ 'paymentToAdd'|trans }}</span><span id="auto-payment-amount">0,00 {{ 'currency.symbol'|trans }}</span></div>
  665.                         <hr>
  666.                         <div class="small">
  667.                             <div><strong>{{ 'paymentStatusLabel'|trans }}:</strong> <span id="auto-payment-status"></span></div>
  668.                             <div><strong>{{ 'paymentTypeLabel'|trans }}:</strong> <span id="auto-payment-method"></span></div>
  669.                             <div><strong>{{ 'description'|trans }}:</strong> <span id="auto-payment-description"></span></div>
  670.                         </div>
  671.                     </div>
  672.                 </div>
  673.                 <div class="modal-footer">
  674.                     <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'cancel'|trans }}</button>
  675.                     <button type="button" class="btn btn-primary" id="confirm-auto-payment">{{ 'confirmAction'|trans }}</button>
  676.                 </div>
  677.             </div>
  678.         </div>
  679.     </div>
  680.     <!-- Hediye Çeki Kullanım Modalı -->
  681.     <div class="modal fade" id="gift-voucher-usage-modal" tabindex="-1" role="dialog" aria-labelledby="giftVoucherUsageModalLabel" aria-hidden="true">
  682.         <div class="modal-dialog modal-dialog-centered" role="document">
  683.             <div class="modal-content">
  684.                 <div class="modal-header">
  685.                     <h5 class="modal-title" id="giftVoucherUsageModalLabel">{{ 'giftVoucherUsage'|trans }}</h5>
  686.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  687.                         <span aria-hidden="true">&times;</span>
  688.                     </button>
  689.                 </div>
  690.                 <div class="modal-body">
  691.                     <div class="alert alert-info">
  692.                         <strong><span id="voucher-customer-name"></span></strong>{{ 'voucherBalanceInfoSuffix'|trans }}: <strong id="voucher-total-balance">0.00 TL</strong>
  693.                     </div>
  694.                     <div class="form-group">
  695.                         <label for="voucher-use-amount">{{ 'amountToUse'|trans }}</label>
  696.                         <input type="number" id="voucher-use-amount" class="form-control" placeholder="0.00" min="0" step="0.01">
  697.                         <small class="form-text text-muted">{{ 'enterAmountToUse'|trans }}</small>
  698.                     </div>
  699.                 </div>
  700.                 <div class="modal-footer">
  701.                     <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'cancel'|trans }}</button>
  702.                     <button type="button" class="btn btn-primary" id="btn-confirm-voucher-usage">{{ 'confirmAndAdd'|trans }}</button>
  703.                 </div>
  704.             </div>
  705.         </div>
  706.     </div>
  707. {% endblock %}
  708. {% block javascript %}
  709. <script src="{{ asset('/decimal.js') }}"></script>
  710.     <script>
  711.         document.addEventListener('DOMContentLoaded', () => {
  712.             const saleDateInput = document.getElementById('sales_form_salesDate');
  713.             if (saleDateInput && !saleDateInput.value) {
  714.                 const now = new Date();
  715.                 const localDate = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().split('T')[0];
  716.                 saleDateInput.value = localDate;
  717.                 saleDateInput.dispatchEvent(new Event('change', { bubbles: true }));
  718.             }
  719.         });
  720.         $(document).ready(function() {
  721.             applyMasks();
  722.             const isDuplicatedProforma = {{ sales is defined and sales.id is not null ? 'true' : 'false' }};
  723.             const duplicatedSoldItems = (function(){
  724.                 if(!isDuplicatedProforma){ return []; }
  725.                 const items = [];
  726.                 {% if sales is defined and sales.productsSolds is defined %}
  727.                     {% for ps in sales.productsSolds %}
  728.                         items.push({
  729.                             productid: {{ ps.product ? ps.product.id : 0 }},
  730.                             productName: '{{ ps.productName|default(ps.product ? ps.product.name : '')|e('js') }}',
  731.                             code: '{{ ps.product ? ps.product.code|e('js') : '' }}',
  732.                             measurement: '{{ ps.measurement is not null ? ps.measurement|e('js') : (ps.product and ps.product.measurement ? ps.product.measurement.name|e('js') : '') }}',
  733.                             quantity: {{ ps.quantity is not null ? ps.quantity|number_format(2, '.', '') : '0' }},
  734.                             unAllocatedQuantity: {{ ps.unAllocatedQuantity is not null ? ps.unAllocatedQuantity|number_format(2, '.', '') : '0' }},
  735.                             unitPrice: {{ ps.totalUnitPurchasePrice is not null ? ps.totalUnitPurchasePrice|number_format(2, '.', '') : '0' }},
  736.                             tax: {{ ps.tax is not null ? ps.tax|number_format(2, '.', '') : '0' }},
  737.                             discount: {{ ps.discount is not null ? ps.discount|number_format(2, '.', '') : '0' }},
  738.                             totalPurchasePrice: {{ ps.totalPuchasePrice is not null ? ps.totalPuchasePrice|number_format(2, '.', '') : (ps.totalPuchasePrice is not null ? ps.totalPuchasePrice|number_format(2, '.', '') : (ps.totalPrice is not null ? ps.totalPrice|number_format(2, '.', '') : '0')) }},
  739.                             totalPrice: {{ ps.totalPrice }},
  740.                             warehouse: {{ sales.warehouse ? sales.warehouse.id : 0 }},
  741.                             thickness: 0,
  742.                             width: 0,
  743.                             height: 0,
  744.                             cost: 0,
  745.                             productType: '{{ ps.product and ps.product.productTypeEnum ? ps.product.productTypeEnum.name|e('js') : '' }}',
  746.                             selectedUnitId: {{ ps.selectedUnit ? ps.selectedUnit.id : 'null' }}
  747.                         });
  748.                     {% endfor %}
  749.                 {% endif %}
  750.                 return items;
  751.             })();
  752.             // TODO: Duplicated items
  753.             async function importDuplicatedSoldItemsIfNeeded(){
  754.                 await getProductsFromWarehouse();
  755.                 if(!isDuplicatedProforma || duplicatedSoldItems.length === 0){
  756.                     return;
  757.                 }
  758.                 const targetWarehouseId = {{ sales is defined and sales.warehouse ? sales.warehouse.id : 0 }};
  759.                 if(targetWarehouseId){
  760.                     $('#warehouseselect').val(String(targetWarehouseId));
  761.                     $('#warehouseselect').trigger('change');
  762.                 }
  763.                 const start = Date.now();
  764.                 const timeoutMs = 15000;
  765.                 const timer = setInterval(function(){
  766.                     if(Array.isArray(products) && products.length > 0){
  767.                         clearInterval(timer);
  768.                         try{
  769.                             duplicatedSoldItems.forEach(function(item){
  770.                                 const p = products.find(function(prod){ return prod.productid === item.productid; });
  771.                                 if(p){
  772.                                     selectProduct(p.productid);
  773.                                 }
  774.                                 addProductToSoldList(
  775.                                     item.productid,
  776.                                     item.productName,
  777.                                     item.code,
  778.                                     item.measurement,
  779.                                     item.quantity,
  780.                                     item.unitPrice,
  781.                                     item.tax,
  782.                                     item.discount,
  783.                                     item.totalPurchasePrice,
  784.                                     item.warehouse,
  785.                                     item.unAllocatedQuantity,
  786.                                     item.thickness,
  787.                                     item.width,
  788.                                     item.height,
  789.                                     item.cost,
  790.                                     item.productType,
  791.                                     item.selectedUnitId
  792.                                 );
  793.                             });
  794.                             fetchSoldListRows();
  795.                             updateTotalAmounts();
  796.                             $('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
  797.                             fetchPaymentForm();
  798.                             fetchStocks();
  799.                         }catch(e){
  800.                             console.error('Duplicate import error', e);
  801.                         }
  802.                     }else if(Date.now() - start > timeoutMs){
  803.                         clearInterval(timer);
  804.                         console.warn('Timed out waiting for products to load');
  805.                     }
  806.                 }, 200);
  807.             }
  808.             if(isDuplicatedProforma){
  809.                 importDuplicatedSoldItemsIfNeeded();
  810.             }
  811.             // =============================================================================
  812.             // INITIALIZATION & GLOBAL VARIABLES
  813.             // =============================================================================
  814.             // Collapse sidebar only on this page
  815.             $('body').addClass('sidebar-toggled');
  816.             $('#accordionSidebar').addClass('toggled');
  817.             // Product data structure
  818.             let product = {
  819.                 code: "",
  820.                 id: 0,
  821.                 length: 0,
  822.                 measurement: "",
  823.                 name: "",
  824.                 priceFob: 0,
  825.                 priceNavlun: 0,
  826.                 productid: 0,
  827.                 purchaseTotalAmount: 0,
  828.                 quantity: 0,
  829.                 thickness: 0,
  830.                 totalQuantity: 0,
  831.                 totalUnitPrice: 0,
  832.                 width: 0,
  833.                 cost: 0,
  834.                 measurementUnit: "",
  835.                 stock: 0,
  836.                 warehouseid: 0,
  837.                 selected: false,
  838.             }
  839.             let products = [];
  840.             // Sold product data structure
  841.             let soldProduct = {
  842.                 productid: 0,
  843.                 productName: "",
  844.                 code: "",
  845.                 measurement: "",
  846.                 quantity: 0,
  847.                 baseUnitQuantity: 0,
  848.                 unAllocatedQuantity: 0,
  849.                 unitPrice: 0,
  850.                 tax: 0,
  851.                 discount: 0,
  852.                 totalPurchasePrice: 0,
  853.                 warehouse: 0,
  854.             }
  855.             let soldList = [];
  856.             const MONEY_ROUNDING_MODE = Decimal.ROUND_HALF_UP;
  857.             // Payment data structure
  858.             let payment = {
  859.                 uuid: 0,
  860.                 id: 0,
  861.                 amount: 0,
  862.                 status: 0,
  863.                 paymentDate: new Date(),
  864.                 paymentDueDate: new Date(),
  865.                 paymentMethod: 0,
  866.                 description: 0,
  867.             }
  868.             let payments = []
  869.             // =============================================================================
  870.             // UTILITY FUNCTIONS
  871.             // =============================================================================
  872.             
  873.              function formatCurrency(amount) {
  874.                 if (amount instanceof Decimal) {
  875.                     amount = amount.toNumber();
  876.                 }
  877.                 return addCommas(amount.toFixed(2)) + ' {{ 'currency.symbol'|trans }}';
  878.             }
  879.             // Convert entered quantity in selected unit to product base unit quantity
  880.             function computeBaseUnitQuantity(productId, selectedUnitId, quantity, onDone){
  881.                 try{
  882.                     const product = products.find(p => p.productid === productId);
  883.                     if(!product){ onDone(quantity); return; }
  884.                     // If unit not selected or already base unit, fallback to same quantity
  885.                     if(!selectedUnitId || String(selectedUnitId) === '' || String(selectedUnitId) === String(product.measurementUnitId)){
  886.                         onDone(quantity);
  887.                         return;
  888.                     }
  889.                     $.ajax({
  890.                         url: '/admin/measurement/convert-to-product-unit',
  891.                         method: 'POST',
  892.                         dataType: 'json',
  893.                         data: {
  894.                             productId: productId,
  895.                             fromUnitId: selectedUnitId,
  896.                             quantity: quantity
  897.                         },
  898.                         success: function(resp){
  899.                             if(resp && resp.success){
  900.                                 onDone(parseFloat(resp.result));
  901.                             } else {
  902.                                 onDone(quantity);
  903.                             }
  904.                         },
  905.                         error: function(){ onDone(quantity); }
  906.                     });
  907.                 }catch(e){ onDone(quantity); }
  908.             }
  909.             // Check if product already exists in sold list with same parameters
  910.             function issetInSoldList(productid, quantity, unitPrice, tax, discount, totalPurchasePrice) {
  911.                 const foundProduct = soldList.find(product =>
  912.                     product.productid === productid &&
  913.                     product.quantity === quantity &&
  914.                     product.unitPrice === unitPrice &&
  915.                     product.tax === tax &&
  916.                     product.discount === discount &&
  917.                     product.totalPurchasePrice === totalPurchasePrice
  918.                 );
  919.                 return !!foundProduct;
  920.             }
  921.             function applyMasks() {
  922.                 $('.mask-money').mask("##0.00", { reverse: true });
  923.             }
  924.             // Convert various input types to Decimal for precise calculations
  925.             function convertToDecimal(value) {
  926.                 try {
  927.                     if (value instanceof Decimal) {
  928.                         return value;
  929.                     }
  930.                     if (value === undefined || value === null) {
  931.                         return new Decimal(0);
  932.                     }
  933.                     if (typeof value === 'number') {
  934.                         if (!isFinite(value) || isNaN(value)) {
  935.                             return new Decimal(0);
  936.                         }
  937.                         return new Decimal(value);
  938.                     }
  939.                     if (typeof value === 'string') {
  940.                         var sanitized = value.replace(/[^0-9.,-]/g, '').replace(/,/g, '').trim();
  941.                         if (sanitized === '' || sanitized === '-' || sanitized === '.' || sanitized === '-.') {
  942.                             return new Decimal(0);
  943.                         }
  944.                         return new Decimal(sanitized);
  945.                     }
  946.                     return new Decimal(0);
  947.                 } catch (e) {
  948.                     return new Decimal(0);
  949.                 }
  950.             }
  951.             // Round value to specified decimal places (default 2)
  952.             function roundToDecimal(value, decimalPlaces = 2) {
  953.                 const decimalValue = new Decimal(value);
  954.                 const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_UP);
  955.                 return roundedValue.toNumber();
  956.             }
  957.             // Round stock values down to prevent overselling
  958.             function roundStockToDecimal(value, decimalPlaces = 2) {
  959.                 const decimalValue = new Decimal(value);
  960.                 const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_DOWN);
  961.                 return roundedValue.toNumber();
  962.             }
  963.             // Round to nearest cent for currency calculations
  964.             function roundToNearestCent(amount) {
  965.                 return Math.round(amount * 100) / 100;
  966.             }
  967.             // Round to two decimal places
  968.             function roundToTwoDecimals(number) {
  969.                 var factor = Math.pow(10, 2);
  970.                 return (Math.round(number * factor) / factor).toFixed(2);
  971.             }
  972.             // Validate numeric input
  973.             function validate(s) {
  974.                 var rgx = /^[0-9]*\.?[0-9]*$/;
  975.                 return s.match(rgx);
  976.             }
  977.             // Convert string to float, handling commas
  978.             function convertToFloat(value) {
  979.                 if (typeof value === "string") {
  980.                     value = value.replace(',', '');
  981.                 }
  982.                 return parseFloat(value);
  983.             }
  984.             // Add thousand separators to numbers
  985.             function addCommas(number) {
  986.                 let decimals = 2;
  987.                 number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
  988.                 var n = !isFinite(+number) ? 0 : +number;
  989.                 var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
  990.                 var sep = ',';
  991.                 var dec = '.';
  992.                 var s = '';
  993.                 var toFixedFix = function(n, prec) {
  994.                     var k = Math.pow(10, prec);
  995.                     return '' + (Math.round(n * k) / k).toFixed(prec);
  996.                 };
  997.                 s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
  998.                 if (s[0].length > 3) {
  999.                     s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
  1000.                 }
  1001.                 if ((s[1] || '').length < prec) {
  1002.                     s[1] = s[1] || '';
  1003.                     s[1] += new Array(prec - s[1].length + 1).join('0');
  1004.                 }
  1005.                 return s.join(dec);
  1006.             }
  1007.             // Create popup window with specified dimensions
  1008.             function createPopup(url, window_name = 'example-popup') {
  1009.                 var width = 1100;
  1010.                 var height = 700;
  1011.                 var screen_width = window.screen.width;
  1012.                 var screen_height = window.screen.height;
  1013.                 var popup_left = (screen_width - width) / 2;
  1014.                 var popup_top = (screen_height - height) / 2;
  1015.                 var popup_window = window.open(url, window_name, 'width=' + width + ',height=' + height + ',left=' + popup_left + ',top=' + popup_top);
  1016.             }
  1017.             // Show error alert using SweetAlert
  1018.             function showErrorAlert(message) {
  1019.                 Swal.fire({
  1020.                     icon: 'error',
  1021.                     title: 'Oops...',
  1022.                     text: '' + message,
  1023.                     background: 'white',
  1024.                 });
  1025.             }
  1026.             // Show success alert using SweetAlert
  1027.             function showSuccessAlert(message) {
  1028.                 Swal.fire({
  1029.                     icon: 'success',
  1030.                     title: 'Başarılı',
  1031.                     text: '' + message,
  1032.                     background: 'white',
  1033.                 });
  1034.             }
  1035.             // =============================================================================
  1036.             // PRODUCT MANAGEMENT FUNCTIONS
  1037.             // =============================================================================
  1038.             // Add product to sold list with validation
  1039.             function addProductToSoldList(
  1040.                 productid,
  1041.                 productName,
  1042.                 code,
  1043.                 measurement,
  1044.                 quantity,
  1045.                 unitPrice,
  1046.                 tax,
  1047.                 discount,
  1048.                 totalPurchasePrice,
  1049.                 warehouseid,
  1050.                 unAllocatedQuantity,
  1051.                 thickness,
  1052.                 width,
  1053.                 height,
  1054.                 cost,
  1055.                 productType,
  1056.                 selectedUnitId = null
  1057.                 ) {
  1058.                 var newQuantity = convertToDecimal(quantity).toNumber();
  1059.                 var newUnAllocatedQuantity = convertToDecimal(unAllocatedQuantity).toNumber();
  1060.                 var newPrice = convertToDecimal(unitPrice).toNumber();
  1061.                 var newTax = convertToDecimal(tax).toNumber();
  1062.                 var newDiscount = convertToDecimal(discount).toNumber();
  1063.                 var newTotalPurchasePrice = convertToDecimal(totalPurchasePrice).toNumber();
  1064.                 if (!issetInSoldList(productid, newQuantity, newPrice, newTax, newDiscount, newTotalPurchasePrice)) {
  1065.                     let productSold = {
  1066.                         uuid: crypto.randomUUID(),
  1067.                         productid: productid,
  1068.                         productName: productName,
  1069.                         code: code,
  1070.                         measurement: measurement,
  1071.                         quantity: newQuantity,
  1072.                         baseUnitQuantity: newQuantity,
  1073.                         unAllocatedQuantity: newUnAllocatedQuantity,
  1074.                         unitPrice: newPrice,
  1075.                         tax: newTax,
  1076.                         discount: newDiscount,
  1077.                         totalPurchasePrice: newTotalPurchasePrice,
  1078.                         warehouse: warehouseid,
  1079.                         thickness: thickness,
  1080.                         width: width,
  1081.                         height: height,
  1082.                         cost: cost,
  1083.                         productType: productType,
  1084.                         selectedUnitId: selectedUnitId,
  1085.                     }
  1086.                     soldList.push(productSold);
  1087.                     // compute base unit quantity asynchronously and update the item
  1088.                     computeBaseUnitQuantity(productid, selectedUnitId, newQuantity, function(baseQty){
  1089.                         const item = soldList.find(i => i.uuid === productSold.uuid);
  1090.                         if(item){ item.baseUnitQuantity = convertToDecimal(baseQty || newQuantity).toNumber(); }
  1091.                     });
  1092.                     fetchStocks();
  1093.                     fetchSoldListRows();
  1094.                     fetchPaymentForm();
  1095.                     $('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
  1096.                     return productSold;
  1097.                 } else {
  1098.                     console.log("List + ")
  1099.                     console.log(soldList)
  1100.                     throw new Error("Ürün zaten listede mevcut !")
  1101.                 }
  1102.             }
  1103.             // Remove product from sold list by UUID
  1104.             function deleteProductInSoldList(uuid) {
  1105.                 soldList = soldList.filter(product =>
  1106.                     product.uuid !== uuid
  1107.                 );
  1108.                 fetchSoldListRows();
  1109.             }
  1110.             // Mark product as selected and unselect others
  1111.             function selectProduct(productId) {
  1112.                 products.forEach(product => product.selected = false);
  1113.                 let product = products.find(product => product.productid === productId);
  1114.                 if (product) {
  1115.                     product.selected = true;
  1116.                 }
  1117.                 return products;
  1118.             }
  1119.             // Get currently selected product
  1120.             function getSelectedProduct() {
  1121.                 return products.find(product => product.selected === true);
  1122.             }
  1123.             // Update product dropdown with available products
  1124.             function updateProductSelect() {
  1125.                 $('#productselect').empty();
  1126.                 $('#productselect').append('<option value="0" data-image="image-not-found.webp">Ürün Seçin</option>');
  1127.                 $.each(products, function(index, value) {
  1128.                     var image = value.image;
  1129.                     var option = '<option value="' + value.id + '" data-image="' + image + '" class="stockoption">' + value.name + ' - ' + value.code + ' - ' + value.measurement + ' - ' + value.quantity + '</option>';
  1130.                     $('#productselect').append(option);
  1131.                 });
  1132.                 $('#productselect').attr('disabled', false);
  1133.             }
  1134.             // Load products from selected warehouse
  1135.             function getProductsFromWarehouse() {
  1136.                 var id = $('#warehouseselect option:selected').val();
  1137.                 if (!id) {
  1138.                     return;
  1139.                 }
  1140.                 $.ajax({
  1141.                     url: "/warehouse-stocks/" + id,
  1142.                     dataType: 'JSON',
  1143.                     method: 'GET',
  1144.                     success: function(response) {
  1145.                         if (response.length > 0) {
  1146.                             if (soldList.length <= 0) {
  1147.                                 products = [];
  1148.                                 $.each(response, function(index, product) {
  1149.                                     let productObj = {
  1150.                                         code: product.code,
  1151.                                         id: product.id,
  1152.                                         image: product.image,
  1153.                                         length: 0,
  1154.                                         measurement: product.measurement,
  1155.                                         name: product.name,
  1156.                                         priceFob: 0,
  1157.                                         priceNavlun: 0,
  1158.                                         productid: product.productid,
  1159.                                         purchaseTotalAmount: 0,
  1160.                                         quantity: product.totalQuantity,
  1161.                                         unAllocatedQuantity: 0,
  1162.                                         thickness: 0,
  1163.                                         totalQuantity: product.totalQuantity,
  1164.                                         totalUnitPrice: 0,
  1165.                                         width: 0,
  1166.                                         cost: 0,
  1167.                                         measurementUnit: product.measurementUnit,
  1168.                                         measurementUnitId: product.measurementUnitId,
  1169.                                         stock: 0,
  1170.                                         warehouseid: $('#warehouseselect option:selected').val(),
  1171.                                         convertibleUnits: [],
  1172.                                         productType: product.productTypeEnum,
  1173.                                     };
  1174.                                     products.push(productObj);
  1175.                                 });
  1176.                                 updateProductSelect();
  1177.                             }
  1178.                         } else {
  1179.                             showErrorAlert('Seçmiş olduğunuz depoda hiç ürün stoğu bulunmamaktadır.');
  1180.                         }
  1181.                     },
  1182.                     error: function(error) {
  1183.                         showErrorAlert('Bilinmeyen bir sistem hatası oluştu lütfen tekrar deneyin.');
  1184.                     }
  1185.                 });
  1186.             }
  1187.             // Update stock quantities based on sold items
  1188.             function fetchStocks() {
  1189.                 // const groupedSoldList = soldList.reduce((acc, soldProduct) => {
  1190.                 //     if (!acc[soldProduct.productid]) {
  1191.                 //         acc[soldProduct.productid] = 0;
  1192.                 //     }
  1193.                 //     acc[soldProduct.productid] += convertToDecimal(soldProduct.quantity).toNumber();
  1194.                 //     return acc;
  1195.                 // }, {});
  1196.                 const groupedSoldList = soldList.reduce((acc, soldProduct) => {
  1197.                     const serviceTypes = ["INSTALLATION_SERVICE", "MAINTENANCE_SERVICE", "CONSULTATION_SERVICE", "TREE"];
  1198.                     const productTypeKey = soldProduct.productType ? (soldProduct.productType.key || soldProduct.productType.name) : null;
  1199.                     const isService = productTypeKey ? serviceTypes.includes(productTypeKey) : false;
  1200.                     if (isService) {
  1201.                         return acc;
  1202.                     }
  1203.                     if (!acc[soldProduct.productid]) {
  1204.                         acc[soldProduct.productid] = 0;
  1205.                     }
  1206.                     const quantityToDeduct = soldProduct.baseUnitQuantity || soldProduct.quantity;
  1207.                     acc[soldProduct.productid] += convertToDecimal(quantityToDeduct).toNumber();
  1208.                     return acc;
  1209.                 }, {});
  1210.                 products.forEach(product => {
  1211.                     if (groupedSoldList[product.productid]) {
  1212.                         const totalSoldQuantity = groupedSoldList[product.productid];
  1213.                         product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity - totalSoldQuantity).toNumber());
  1214.                     } else {
  1215.                         product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity).toNumber());
  1216.                     }
  1217.                 });
  1218.                 calculateAndWriteDiffBetweenPaymentAndSolds();
  1219.             }
  1220.             // Validate stock quantity before adding to sold list
  1221.             // function checkStockQuantity(quantity) {
  1222.             //     let product = products.find(p => p.productid === parseInt(productId, 10));
  1223.             //     const productType = product.productType.key;
  1224.             //     if(productType === "INSTALLATION_SERVICE" || productType === "MAINTENANCE_SERVICE" || productType === "CONSULTATION_SERVICE" || productType === 'TREE'){
  1225.             //
  1226.             //         $('#updateServiceModal').modal('show');
  1227.             //
  1228.             //     }else{
  1229.             //      if (convertToFloat(quantity) > product.quantity) {
  1230.             //         throw new Error('Yetersiz stok. Ekeleyebileceğiniz maksimum stok: ' + product.quantity);
  1231.             //         resetAddProductForm();
  1232.             //      }
  1233.             //     }
  1234.             //
  1235.             // }
  1236.             function checkStockQuantity() {
  1237.                 return new Promise((resolve, reject) => {
  1238.                     const selectedProduct = getSelectedProduct();
  1239.                     if (!selectedProduct) {
  1240.                         return reject(new Error('Lütfen bir ürün seçin.'));
  1241.                     }
  1242.                     const productType = selectedProduct.productType.key;
  1243.                     const isService = ["INSTALLATION_SERVICE", "MAINTENANCE_SERVICE", "CONSULTATION_SERVICE", "TREE"].includes(productType);
  1244.                     if (isService) {
  1245.                         // Eğer ürün bir hizmet ise, stok kontrolü yapma, işlemi onayla.
  1246.                         return resolve();
  1247.                     }
  1248.                     const quantityStr = $('#adder-allocated-quantity').val();
  1249.                     const fromUnitId = $('#adder-quantity-unit-select').val();
  1250.                     const requestedQuantity = convertToDecimal(quantityStr).toNumber();
  1251.                     // Eğer birim seçilmemişse veya miktar 0 ise, miktar kadarını stokla karşılaştır.
  1252.                     if (!fromUnitId || fromUnitId === '') {
  1253.                         if (requestedQuantity > selectedProduct.quantity) {
  1254.                             return reject(new Error(`Yetersiz stok. Eklenebilecek maksimum miktar: ${selectedProduct.quantity} ${selectedProduct.measurementUnit}`));
  1255.                         }
  1256.                         return resolve(requestedQuantity); // Temel birimdeki miktarı döndür
  1257.                     }
  1258.                     // Seçilen birimdeki miktarı, ürünün temel birimine çevir.
  1259.                     $.ajax({
  1260.                         url: '/admin/measurement/convert-to-product-unit',
  1261.                         method: 'POST',
  1262.                         dataType: 'json',
  1263.                         data: {
  1264.                             productId: selectedProduct.productid,
  1265.                             fromUnitId: fromUnitId,
  1266.                             quantity: requestedQuantity
  1267.                         },
  1268.                         success: function(resp) {
  1269.                             if (resp && resp.success) {
  1270.                                 const requestedBaseQuantity = convertToDecimal(resp.result).toNumber();
  1271.                                 // Temel birimdeki talep edilen miktar, stoktan büyük mü diye kontrol et.
  1272.                                 if (requestedBaseQuantity > selectedProduct.quantity) {
  1273.                                     reject(new Error(`Yetersiz stok. Talep edilen miktar (${quantityStr} ${$('#adder-quantity-unit-select option:selected').text()}), stoktaki ${selectedProduct.quantity} ${selectedProduct.measurementUnit}'den fazla.`));
  1274.                                 } else {
  1275.                                     resolve(requestedBaseQuantity); // Kontrol başarılı, temel birimdeki miktarı döndür.
  1276.                                 }
  1277.                             } else {
  1278.                                 reject(new Error('Birim dönüştürme sırasında bir hata oluştu. Stok kontrol edilemedi.'));
  1279.                             }
  1280.                         },
  1281.                         error: function() {
  1282.                             reject(new Error('Stok kontrolü sırasında sunucuya ulaşılamadı.'));
  1283.                         }
  1284.                     });
  1285.                 });
  1286.             }
  1287.             // Get product cost information from server
  1288.             function getProductCost(pid) {
  1289.                 const url = '/product-cost/' + pid + '/' + getSelectedProduct().warehouseid;
  1290.                 $.ajax({
  1291.                     url: "/product-cost-detail/" + pid + '?warehouse=' + getSelectedProduct().warehouseid,
  1292.                     method: "POST",
  1293.                     dataType: "JSON",
  1294.                     success: function(data) {
  1295.                         var product = getSelectedProduct();
  1296.                         product.cost = data.cost;
  1297.                         product.measurementUnit = data.measurement;
  1298.                         $('.stock-info-span').html(data.stock.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " " + data.measurement);
  1299.                         $('.cost-info-span').html(data.cost.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " €");
  1300.                         $('.measurement-unit-info-span').html(data.measurement);
  1301.                         $('#hidden-cost-input').val(data.cost);
  1302.                         const detailsHtml = `
  1303.                             <div class="row align-items-center">
  1304.                                 <div class="col-md-3">${product.name}</div>
  1305.                                 <div class="col-md-3">
  1306.                                     <strong>
  1307.                                         <button type="button" class="btn btn-link btn-sm p-0 cost-popup-btn" data-url="${url}">Maliyet: ${data.cost.toFixed(2)} €</button>
  1308.                                     </strong>
  1309.                                 </div>
  1310.                                 <div class="col-md-3"><strong>Stok:</strong> ${data.stock.toFixed(2)} ${data.measurement}</div>
  1311.                                 <div class="col-md-3"><strong>Birim:</strong> ${data.measurement}</div>
  1312.                             </div>
  1313.                         `;
  1314.                         $('#info-details-content').html(detailsHtml);
  1315.                         return data;
  1316.                     },
  1317.                     error: function(data) {
  1318.                         $('#info-details-content').html('<div class="text-danger">Ürün maliyet bilgileri yüklenemedi.</div>');
  1319.                     }
  1320.                 });
  1321.             }
  1322.             // Generate HTML for a product row (Standard or AVOIR)
  1323.             function generateProductRowHtml(value, options = {}) {
  1324.                 const isAvoir = options.isAvoir || false;
  1325.                 const readonly = isAvoir ? 'readonly disabled' : '';
  1326.                 const inputReadonly = isAvoir ? 'readonly' : ''; // For inputs we might want just readonly, not disabled
  1327.                 const rowClass = isAvoir ? 'bg-light text-danger' : '';
  1328.                 const uuid = value.uuid;
  1329.                 // Render Service Button (only for standard rows)
  1330.                 const serviceBtnHtml = !isAvoir ? renderServiceEditBtn(value) : '';
  1331.                 
  1332.                 // Actions
  1333.                 let actionButtonsHtml = '';
  1334.                 if (!isAvoir) {
  1335.                     actionButtonsHtml = `
  1336.                         <div class="btn-group" role="group">
  1337.                             ${serviceBtnHtml}
  1338.                             <button type="button" class="btn btn-outline-secondary btn-sm open-unallocated-modal-row"
  1339.                                     data-uuid="${uuid}" title="{% trans %}unAllocatedQuantity{% endtrans %}">
  1340.                                 <i class="fas fa-box-open"></i>
  1341.                                 <span class="badge badge-light ml-1 unalloc-display">${value.unAllocatedQuantity.toFixed(2)}</span>
  1342.                             </button>
  1343.                             <button type="button" class="btn btn-danger btn-sm delete-row-button"
  1344.                                     data-uuid="${uuid}" title="Sil">
  1345.                                 <i class="fas fa-trash"></i>
  1346.                             </button>
  1347.                         </div>
  1348.                     `;
  1349.                 } else {
  1350.                      // Empty for AVOIR
  1351.                      actionButtonsHtml = '';
  1352.                 }
  1353.                 // Info Button
  1354.                 let infoButtonHtml = '';
  1355.                 if (!isAvoir) {
  1356.                      infoButtonHtml = `
  1357.                         <button type="button" class="btn btn-sm btn-info btn-icon show-row-info-btn"
  1358.                                 data-uuid="${uuid}" title="Ürün Bilgileri">
  1359.                             <i class="fas fa-info-circle"></i>
  1360.                         </button>
  1361.                      `;
  1362.                 }
  1363.                 // Product Select / Name
  1364.                 let productSelectHtml = '';
  1365.                 if (isAvoir) {
  1366.                     // For AVOIR, we just show a static input or disabled select mimicking the look
  1367.                     productSelectHtml = `
  1368.                         <input type="text" class="form-control text-danger font-weight-bold" value="AVOIR" readonly>
  1369.                     `;
  1370.                 } else {
  1371.                      let productOptions = '';
  1372.                      const serviceTypes = ['INSTALLATION_SERVICE','MAINTENANCE_SERVICE','CONSULTATION_SERVICE','TREE'];
  1373.                      const isServiceRow = (function(pt){
  1374.                         try {
  1375.                             if (!pt) return false;
  1376.                             if (typeof pt === 'string') return serviceTypes.includes(pt);
  1377.                             const key = pt.key || pt.name;
  1378.                             return serviceTypes.includes(key);
  1379.                         } catch(e) { return false; }
  1380.                      })(value.productType);
  1381.                      products.forEach(function(product) {
  1382.                         const isSelected = product.productid === value.productid ? 'selected' : '';
  1383.                         const displayName = (isSelected && isServiceRow && value.productName) ? value.productName : product.name;
  1384.                         productOptions += `<option value="${product.productid}" ${isSelected}>${displayName} - ${product.measurement} - ${product.quantity} - ${product.code}</option>`;
  1385.                      });
  1386.                      productSelectHtml = `
  1387.                         <select name="productId[]" class="form-control product-select-row" data-uuid="${uuid}">
  1388.                              ${productOptions}
  1389.                         </select>
  1390.                      `;
  1391.                 }
  1392.                 
  1393.                 // Unallocated Hidden Inputs
  1394.                 const unAllocatedHidens = !isAvoir ? `
  1395.                     <input type="hidden" name="warehouseid[]" value="${value.warehouse}">
  1396.                     <input type="hidden" name="length[]" value="${value.length || ''}">
  1397.                     <input type="hidden" name="width[]" value="${value.width || ''}">
  1398.                     <input type="hidden" name="thickness[]" value="${value.thickness || ''}">
  1399.                     <input type="hidden" name="cost[]" value="${value.cost || ''}">
  1400.                     <input type="hidden" name="unAllocatedQuantity[]" class="unallocated-quantity-hidden" value="${value.unAllocatedQuantity}" data-uuid="${uuid}">
  1401.                     <input type="hidden" name="unAllocatedQuantityUnit[]" class="unallocated-unit-hidden" value="" data-uuid="${uuid}">
  1402.                 ` : '';
  1403.                 // Quantity Unit Select
  1404.                 let quantityUnitSelectHtml = '';
  1405.                 if(isAvoir){
  1406.                      quantityUnitSelectHtml = `
  1407.                         <select class="custom-select custom-select-sm" disabled>
  1408.                             <option>-</option>
  1409.                         </select>
  1410.                      `;
  1411.                 } else {
  1412.                     quantityUnitSelectHtml = `
  1413.                         <select class="custom-select custom-select-sm quantity-unit-select"
  1414.                                 name="quantityUnit[]" data-uuid="${uuid}">
  1415.                             <option value="">-</option>
  1416.                         </select>
  1417.                     `;
  1418.                 }
  1419.                 // Discount Options
  1420.                 let discountOptionsHtml = '';
  1421.                 const discounts = [0, 1, 2, 3, 4, 5, 20, 30, 100];
  1422.                 discounts.forEach(function(d){
  1423.                     discountOptionsHtml += `<option value="${d}" ${value.discount === d ? 'selected' : ''}>% ${d}</option>`;
  1424.                 });
  1425.                 
  1426.                 // Tax Options
  1427.                 let taxOptionsHtml = '';
  1428.                 const taxes = [0, 10, 20];
  1429.                 taxes.forEach(function(t){
  1430.                     const disabledAttr = t === 0 ? 'disabled' : '';
  1431.                     taxOptionsHtml += `<option value="${t}" ${value.tax === t ? 'selected' : ''} ${disabledAttr}>% ${t}</option>`;
  1432.                 });
  1433.                 const quantityVal = value.quantity !== undefined ? value.quantity.toFixed(2) : '1.00';
  1434.                 const unitPriceVal = value.unitPrice !== undefined ? value.unitPrice.toFixed(2) : '0.00';
  1435.                 const totalPriceVal = value.totalPurchasePrice !== undefined ? value.totalPurchasePrice.toFixed(2) : '0.00';
  1436.                 // Only standard rows get the name="..." attributes to be submitted
  1437.                 const nameAttr = isAvoir ? '' : 'name=';
  1438.                 return `
  1439.                     <tr id="${uuid}" data-product-id="${value.productid || ''}" class="${rowClass}">
  1440.                         <td>${infoButtonHtml}</td>
  1441.                         <td class="product-select-cell">
  1442.                             ${productSelectHtml}
  1443.                             ${unAllocatedHidens}
  1444.                         </td>
  1445.                         <td>
  1446.                             <div class="input-group input-group-sm" style="max-width: 220px;">
  1447.                                 <input type="number" ${!isAvoir ? 'name="quantity[]"' : ''}
  1448.                                        class="form-control quantity-input-row mask-money ${isAvoir ? 'text-danger' : ''}"
  1449.                                        value="${quantityVal}"
  1450.                                        min="0" step="0.01" data-uuid="${uuid}" ${inputReadonly}>
  1451.                                 <div class="input-group-append">
  1452.                                     ${quantityUnitSelectHtml}
  1453.                                 </div>
  1454.                             </div>
  1455.                         </td>
  1456.                         <td>
  1457.                             <input type="text" ${!isAvoir ? 'name="unitPrice[]"' : ''}
  1458.                                    class="form-control form-control-sm price-input-row mask-money ${isAvoir ? 'text-danger' : ''}"
  1459.                                    value="${unitPriceVal}"
  1460.                                    min="0" step="0.01" data-uuid="${uuid}"
  1461.                                    placeholder="Birim Fiyat" ${inputReadonly}>
  1462.                         </td>
  1463.                         <td>
  1464.                             <select ${!isAvoir ? 'name="discount[]"' : ''} class="form-control form-control-sm discount-select-row"
  1465.                                     data-uuid="${uuid}" ${readonly}>
  1466.                                 ${discountOptionsHtml}
  1467.                             </select>
  1468.                         </td>
  1469.                         <td>
  1470.                             <select ${!isAvoir ? 'name="tax[]"' : ''} class="form-control form-control-sm tax-select-row"
  1471.                                     data-uuid="${uuid}" ${readonly}>
  1472.                                 ${taxOptionsHtml}
  1473.                             </select>
  1474.                         </td>
  1475.                         <td class="text-right">
  1476.                             <input ${!isAvoir ? 'name="totalPrice[]"' : ''} type="text"
  1477.                                    class="form-control form-control-sm total-input-row mask-money ${isAvoir ? 'text-danger font-weight-bold' : ''}"
  1478.                                    value="${totalPriceVal}"
  1479.                                    data-uuid="${uuid}"
  1480.                                    placeholder="Toplam" ${inputReadonly}/>
  1481.                         </td>
  1482.                         <td class="text-center">
  1483.                             ${actionButtonsHtml}
  1484.                         </td>
  1485.                     </tr>
  1486.                     ${!isAvoir ? `
  1487.                     <tr class="info-details-row-class" data-uuid="${uuid}" style="display: none;">
  1488.                         <td colspan="8" style="padding: 0; border-top: none;">
  1489.                             <div class="info-details-content-class p-3" style="display: none;">
  1490.                                 <div class="row">
  1491.                                     <div class="col-md-3"><strong>Ürün Adı:</strong> ${value.productName}</div>
  1492.                                     <div class="col-md-3"><strong>Kod:</strong> ${value.code}</div>
  1493.                                     <div class="col-md-3"><strong>Ölçü:</strong> ${value.measurement}</div>
  1494.                                     <div class="col-md-3"><strong>Uzunluk:</strong> ${value.length || '-'}</div>
  1495.                                 </div>
  1496.                                 <div class="row mt-2">
  1497.                                     <div class="col-md-3"><strong>Genişlik:</strong> ${value.width || '-'}</div>
  1498.                                     <div class="col-md-3"><strong>Kalınlık:</strong> ${value.thickness || '-'}</div>
  1499.                                     <div class="col-md-3"><strong>Maliyet:</strong> ${(value.cost || 0).toFixed(2)} €</div>
  1500.                                     <div class="col-md-3"><strong>Depo:</strong> ${value.warehouse}</div>
  1501.                                 </div>
  1502.                             </div>
  1503.                         </td>
  1504.                     </tr>` : ''}
  1505.                 `;
  1506.             }
  1507.             // Render sold products table rows with complete editing capabilities
  1508.             // TODO: Seçilen ürün eğer hizmet ise dropdown da hizmet adını kaydedildiği şekilde göster
  1509.             function fetchSoldListRows() {
  1510.                 $('#product-list-table-tbody').empty();
  1511.                 soldList.forEach(function(value, index) {
  1512.                     const rowHtml = generateProductRowHtml(value, { isAvoir: false });
  1513.                     $('#product-list-table-tbody').append(rowHtml);
  1514.                     $(`.product-select-row[data-uuid="${value.uuid}"]`).select2({
  1515.                         theme: 'bootstrap4',
  1516.                         dropdownCssClass: 'custom-select-dropdown',
  1517.                         placeholder: 'Ürün Seçin',
  1518.                         allowClear: true
  1519.                     });
  1520.                     applyMasks();
  1521.                     // Load measurement units for each product row with default unit
  1522.                     const product = products.find(p => p.productid === value.productid);
  1523.                     const defaultUnitId = product ? product.measurementUnitId : null;
  1524.                     const preferredUnitId = (value.selectedUnitId !== undefined && value.selectedUnitId !== null && value.selectedUnitId !== '') ? value.selectedUnitId : defaultUnitId;
  1525.                     loadMeasurementUnitsForRow(value.productid, value.uuid, preferredUnitId);
  1526.                 });
  1527.                 // Check for Gift Voucher amount and add AVOIR row using reusable component
  1528.                 const totalVoucherAmount = calculateTotalGiftVoucherAmount();
  1529.                 if (!totalVoucherAmount.isZero()) {
  1530.                      const avoirItem = {
  1531.                         uuid: 'avoir-row',
  1532.                         productid: null,
  1533.                         productName: 'AVOIR',
  1534.                         quantity: 1,
  1535.                         unAllocatedQuantity: 0,
  1536.                         unitPrice: totalVoucherAmount.times(-1).toNumber(), // Negative unit price
  1537.                         totalPurchasePrice: totalVoucherAmount.times(-1).toNumber(), // Negative total
  1538.                         discount: 0,
  1539.                         tax: 0
  1540.                      };
  1541.                      
  1542.                      // We override the visual display of the price to be positive in the input if desired, 
  1543.                      // OR we keep it negative to indicate deduction. 
  1544.                      // The user request screenshot showed "AVOIR" in red. 
  1545.                      // Standard AVOIR usually implies negative context but let's stick to the styling.
  1546.                      // IMPORTANT: The screenshot usually shows the total as negative, so unit price should likely be negative too.
  1547.                      // Wait, if Quantity is 1 and Total is -Amount, Unit Price must be -Amount.
  1548.                      
  1549.                      const avoirRowHtml = generateProductRowHtml(avoirItem, { isAvoir: true });
  1550.                      $('#product-list-table-tbody').append(avoirRowHtml);
  1551.                 }
  1552.                 fetchStocks();
  1553.                 updateProductSelect();
  1554.                 updateTotalAmounts();
  1555.                 $('#warehouseselect').attr('disabled', soldList.length > 0);
  1556.             }
  1557.             // --- GIFT VOUCHER INTEGRATION ---
  1558.             // Set default ID to 0 or a value that won't match if not found.
  1559.             // Using Twig to find the ID.
  1560.             const PAYMENT_METHOD_GIFT_VOUCHER_ID = (function() {
  1561.                 {% for pm in paymentMethods %}
  1562.                     {% if pm.name == 'Hediye Çeki' or pm.name == 'Gift Voucher' %}
  1563.                         return "{{ pm.id }}";
  1564.                     {% endif %}
  1565.                 {% endfor %}
  1566.                 return null;
  1567.             })();
  1568.             function calculateTotalGiftVoucherAmount() {
  1569.                 let totalVoucher = new Decimal(0);
  1570.                 if (!PAYMENT_METHOD_GIFT_VOUCHER_ID) return totalVoucher;
  1571.                 payments.forEach(function(p) {
  1572.                     if (String(p.paymentMethod) === String(PAYMENT_METHOD_GIFT_VOUCHER_ID)) {
  1573.                         totalVoucher = totalVoucher.plus(convertToDecimal(p.amount));
  1574.                     }
  1575.                 });
  1576.                 return totalVoucher;
  1577.             }
  1578.             // ---------------------------------
  1579.             function renderServiceEditBtn(value){
  1580.               const product = products.find(p => p.productid === value.productid);
  1581.               if (isProductService(product.productid)) {
  1582.                 return `
  1583.                   <button type="button"
  1584.                           class="btn btn-warning btn-sm edit-service-button"
  1585.                           data-uuid="${value.uuid}"
  1586.                           title="Servisi Güncelle">
  1587.                     <i class="fas fa-edit"></i>
  1588.                   </button>
  1589.                 `;
  1590.               }
  1591.               return ''; // değilse hiç ekleme
  1592.             }
  1593.             function calculateRowFinancials(source) {
  1594.                 const quantity = convertToDecimal(source.quantity || 0);
  1595.                 const unAllocatedQuantity = convertToDecimal(source.unAllocatedQuantity || 0);
  1596.                 const unitPrice = convertToDecimal(source.unitPrice || 0);
  1597.                 const discountPercentage = convertToDecimal(source.discount || 0);
  1598.                 const taxPercentage = convertToDecimal(source.tax || 0);
  1599.                 const totalQuantity = quantity.plus(unAllocatedQuantity);
  1600.                 if (totalQuantity.isZero()) {
  1601.                     return {
  1602.                         subtotal: new Decimal(0),
  1603.                         discountAmount: new Decimal(0),
  1604.                         afterDiscount: new Decimal(0),
  1605.                         taxAmount: new Decimal(0),
  1606.                         total: new Decimal(0),
  1607.                         totalRounded: new Decimal(0),
  1608.                         taxPercentage
  1609.                     };
  1610.                 }
  1611.                 const subtotal = totalQuantity.mul(unitPrice);
  1612.                 const discountAmount = subtotal.mul(discountPercentage.div(100));
  1613.                 const afterDiscount = subtotal.minus(discountAmount);
  1614.                 const taxAmount = afterDiscount.mul(taxPercentage.div(100));
  1615.                 const total = afterDiscount.plus(taxAmount);
  1616.                 return {
  1617.                     subtotal,
  1618.                     discountAmount,
  1619.                     afterDiscount,
  1620.                     taxAmount,
  1621.                     total,
  1622.                     totalRounded: total.toDecimalPlaces(2, MONEY_ROUNDING_MODE),
  1623.                     taxPercentage
  1624.                 };
  1625.             }
  1626.             function aggregateSoldTotals() {
  1627.                 const aggregation = {
  1628.                     grossTotal: new Decimal(0),
  1629.                     totalDiscount: new Decimal(0),
  1630.                     subtotal: new Decimal(0),
  1631.                     tax: new Decimal(0),
  1632.                     grandTotal: new Decimal(0),
  1633.                     byTaxRate: {}
  1634.                 };
  1635.                 soldList.forEach(function(item) {
  1636.                     // item.totalPurchasePrice'ı kullan (manuel veya otomatik hesaplanmış)
  1637.                     const itemTotal = convertToDecimal(item.totalPurchasePrice || 0);
  1638.                     // Toplam üzerinden geriye doğru hesaplama
  1639.                     // Formül: total = subtotal × (1 - discount%) × (1 + tax%)
  1640.                     // Ters formül: afterDiscount = total / (1 + tax%)
  1641.                     //              subtotal = afterDiscount / (1 - discount%)
  1642.                     const taxPercentage = convertToDecimal(item.tax || 0);
  1643.                     const discountPercentage = convertToDecimal(item.discount || 0);
  1644.                     const taxMultiplier = new Decimal(1).plus(taxPercentage.div(100));
  1645.                     // Adım 1: KDV'yi çıkar → İndirim sonrası tutarı bul
  1646.                     // afterDiscount = total / (1 + tax%)
  1647.                     const afterDiscount = itemTotal.div(taxMultiplier);
  1648.                     // Adım 2: KDV tutarını hesapla
  1649.                     // taxAmount = afterDiscount × tax%
  1650.                     const taxAmount = afterDiscount.mul(taxPercentage.div(100));
  1651.                     // NOT: "Ara Toplam" (subtotal) = afterDiscount (indirim uygulanmış tutar)
  1652.                     // Çünkü görüntülenen "Ara Toplam" indirim sonrası, KDV öncesi tutar
  1653.                     // Calculate Gross (before discount)
  1654.                     const quantity = convertToDecimal(item.quantity).plus(convertToDecimal(item.unAllocatedQuantity));
  1655.                     const unitPrice = convertToDecimal(item.unitPrice); 
  1656.                     const grossLine = quantity.mul(unitPrice);
  1657.                     
  1658.                     // Calculate Discount derived from Gross vs Net
  1659.                     const discountAmount = grossLine.minus(afterDiscount);
  1660.                     aggregation.grossTotal = aggregation.grossTotal.plus(grossLine);
  1661.                     aggregation.totalDiscount = aggregation.totalDiscount.plus(discountAmount);
  1662.                     aggregation.subtotal = aggregation.subtotal.plus(afterDiscount);
  1663.                     aggregation.tax = aggregation.tax.plus(taxAmount);
  1664.                     aggregation.grandTotal = aggregation.grandTotal.plus(itemTotal);
  1665.                     const rateKey = taxPercentage.toFixed(4);
  1666.                     if (!aggregation.byTaxRate[rateKey]) {
  1667.                         aggregation.byTaxRate[rateKey] = {
  1668.                             rate: taxPercentage,
  1669.                             amount: new Decimal(0)
  1670.                         };
  1671.                     }
  1672.                     aggregation.byTaxRate[rateKey].amount = aggregation.byTaxRate[rateKey].amount.plus(taxAmount);
  1673.                 });
  1674.                 aggregation.subtotalRounded = aggregation.subtotal.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
  1675.                 aggregation.taxRounded = aggregation.tax.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
  1676.                 aggregation.grandTotalRounded = aggregation.grandTotal.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
  1677.                 return aggregation;
  1678.             }
  1679.             // Track which row is being updated to prevent circular updates
  1680.             let rowUpdatingFromTotal = {};
  1681.             let rowUpdatingFromUnitPrice = {};
  1682.             // Track rows where total was manually entered (should not recalculate total)
  1683.             let rowHasManualTotal = {};
  1684.             function updateSoldProductFromRow(uuid) {
  1685.                 const itemIndex = soldList.findIndex(item => item.uuid === uuid);
  1686.                 if (itemIndex === -1) {
  1687.                     return;
  1688.                 }
  1689.                 // Eğer bu satır toplam tarafından güncelleniyorsa, tekrar toplam güncelleme
  1690.                 if(rowUpdatingFromTotal[uuid]) {
  1691.                     return;
  1692.                 }
  1693.                 rowUpdatingFromUnitPrice[uuid] = true;
  1694.                 const $row = $(`#${uuid}`);
  1695.                 const quantity = convertToDecimal($row.find('.quantity-input-row').val()).toNumber();
  1696.                 const unitPrice = convertToDecimal($row.find('.price-input-row').val()).toNumber();
  1697.                 const discount = convertToDecimal($row.find('.discount-select-row').val()).toNumber();
  1698.                 const tax = convertToDecimal($row.find('.tax-select-row').val()).toNumber();
  1699.                 soldList[itemIndex].quantity = quantity;
  1700.                 soldList[itemIndex].unitPrice = unitPrice;
  1701.                 soldList[itemIndex].discount = discount;
  1702.                 soldList[itemIndex].tax = tax;
  1703.                 const selectedUnitId = $row.find('.quantity-unit-select').val();
  1704.                 computeBaseUnitQuantity(soldList[itemIndex].productid, selectedUnitId, quantity, function(baseQty){
  1705.                     soldList[itemIndex].baseUnitQuantity = convertToDecimal(baseQty || quantity).toNumber();
  1706.                 });
  1707.                 // Eğer bu satırda manuel toplam girildiyse, toplam sabit kalmalı, sadece birim fiyat güncellensin
  1708.                 if (rowHasManualTotal[uuid]) {
  1709.                     // Manuel toplam korunuyor, birim fiyatı tersine hesapla
  1710.                     const manualTotal = convertToDecimal(soldList[itemIndex].totalPurchasePrice);
  1711.                     const totalQuantity = convertToDecimal(quantity).plus(convertToDecimal(soldList[itemIndex].unAllocatedQuantity || 0));
  1712.                     if (!totalQuantity.isZero()) {
  1713.                         const discountMultiplier = new Decimal(1).minus(convertToDecimal(discount).div(100));
  1714.                         const taxMultiplier = new Decimal(1).plus(convertToDecimal(tax).div(100));
  1715.                         if (discountMultiplier.isZero()) {
  1716.                             soldList[itemIndex].unitPrice = 0;
  1717.                             $row.find('.price-input-row').val('0.00');
  1718.                         } else if (!taxMultiplier.isZero()) {
  1719.                             const calculatedUnitPrice = manualTotal
  1720.                                 .div(taxMultiplier)
  1721.                                 .div(discountMultiplier)
  1722.                                 .div(totalQuantity)
  1723.                                 .toDecimalPlaces(6, MONEY_ROUNDING_MODE);
  1724.                             soldList[itemIndex].unitPrice = calculatedUnitPrice.toNumber();
  1725.                             $row.find('.price-input-row').val(calculatedUnitPrice.toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
  1726.                         }
  1727.                     }
  1728.                     // Toplam input'u DEĞİŞTİRME - manuel değer korunuyor
  1729.                 } else {
  1730.                     // Normal hesaplama - toplam otomatik hesaplansın
  1731.                     const rowTotals = calculateRowFinancials(soldList[itemIndex]);
  1732.                     soldList[itemIndex].totalPurchasePrice = rowTotals.totalRounded.toNumber();
  1733.                     $row.find('.total-input-row').val(rowTotals.totalRounded.toFixed(2));
  1734.                 }
  1735.                 updateTotalAmounts();
  1736.                 fetchStocks();
  1737.                 fetchPaymentForm();
  1738.                 setTimeout(function(){ delete rowUpdatingFromUnitPrice[uuid]; }, 0);
  1739.             }
  1740.             // Toplam tutar değiştirildiğinde birim fiyatı tersine hesaplar
  1741.             function updateUnitPriceFromTotal(uuid) {
  1742.                 const itemIndex = soldList.findIndex(item => item.uuid === uuid);
  1743.                 if (itemIndex === -1) {
  1744.                     return;
  1745.                 }
  1746.                 // Eğer bu satır birim fiyat tarafından güncelleniyorsa, döngüyü engelle
  1747.                 if(rowUpdatingFromUnitPrice[uuid]) {
  1748.                     return;
  1749.                 }
  1750.                 rowUpdatingFromTotal[uuid] = true;
  1751.                 // ÖNEMLİ: Toplam manuel girildi, bu satırı işaretle
  1752.                 rowHasManualTotal[uuid] = true;
  1753.                 const $row = $(`#${uuid}`);
  1754.                 const totalValue = convertToDecimal($row.find('.total-input-row').val());
  1755.                 const item = soldList[itemIndex];
  1756.                 const totalQuantity = convertToDecimal(item.quantity || 0).plus(convertToDecimal(item.unAllocatedQuantity || 0));
  1757.                 if (totalQuantity.isZero()) {
  1758.                     setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
  1759.                     return;
  1760.                 }
  1761.                 // Manuel girilen toplam değerini kaydet
  1762.                 soldList[itemIndex].totalPurchasePrice = totalValue.toNumber();
  1763.                 // Eğer toplam 0 ise, birim fiyat da 0 olmalı
  1764.                 if (totalValue.isZero()) {
  1765.                     $row.find('.price-input-row').val('0.00');
  1766.                     soldList[itemIndex].unitPrice = 0;
  1767.                     updateTotalAmounts();
  1768.                     fetchStocks();
  1769.                     fetchPaymentForm();
  1770.                     setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
  1771.                     return;
  1772.                 }
  1773.                 const discountMultiplier = new Decimal(1).minus(convertToDecimal(item.discount || 0).div(100));
  1774.                 const taxMultiplier = new Decimal(1).plus(convertToDecimal(item.tax || 0).div(100));
  1775.                 // %100 indirimde discountMultiplier = 0 olur, bu durumda birim fiyat 0 olmalı
  1776.                 if (discountMultiplier.isZero()) {
  1777.                     $row.find('.price-input-row').val('0.00');
  1778.                     soldList[itemIndex].unitPrice = 0;
  1779.                     updateTotalAmounts();
  1780.                     fetchStocks();
  1781.                     fetchPaymentForm();
  1782.                     setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
  1783.                     return;
  1784.                 }
  1785.                 if (taxMultiplier.isZero()) {
  1786.                     setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
  1787.                     return;
  1788.                 }
  1789.                 // Formül: unitPrice = total / ((1 + tax%) * (1 - discount%) * quantity)
  1790.                 const unitPrice = totalValue
  1791.                     .div(taxMultiplier)
  1792.                     .div(discountMultiplier)
  1793.                     .div(totalQuantity)
  1794.                     .toDecimalPlaces(6, MONEY_ROUNDING_MODE);
  1795.                 $row.find('.price-input-row').val(unitPrice.toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
  1796.                 soldList[itemIndex].unitPrice = unitPrice.toNumber();
  1797.                 // Sadece stok ve genel toplamları güncelle, satır toplamını değiştirme
  1798.                 updateTotalAmounts();
  1799.                 fetchStocks();
  1800.                 fetchPaymentForm();
  1801.                 setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
  1802.             }
  1803.             // =============================================================================
  1804.             // WAREHOUSE SELECTION EVENT HANDLER
  1805.             // =============================================================================
  1806.             // Handle warehouse selection change
  1807.             $('#warehouseselect').on('change', function() {
  1808.                 getProductsFromWarehouse();
  1809.             });
  1810.             // Auto-select warehouse if there is only one
  1811.             var $warehouseSelect = $('#warehouseselect');
  1812.             if ($warehouseSelect.find('option').length === 2 && $warehouseSelect.val() === "") {
  1813.                  var firstWarehouseVal = $warehouseSelect.find('option').eq(1).val();
  1814.                  $warehouseSelect.val(firstWarehouseVal).trigger('change');
  1815.             }
  1816.             // Load measurement units for a specific product row (with caching)
  1817.             let measurementUnitsCache = {};
  1818.             function loadMeasurementUnitsForRow(productId, uuid, defaultUnitId = null) {
  1819.                 // Check cache first
  1820.                 if (measurementUnitsCache[productId]) {
  1821.                     populateUnitSelectForRow(uuid, measurementUnitsCache[productId], defaultUnitId);
  1822.                     return;
  1823.                 }
  1824.                 // Load from server if not cached
  1825.                 $.get('/product/' + productId + '/measurement-units', function(units) {
  1826.                     // Cache the units
  1827.                     measurementUnitsCache[productId] = units || [];
  1828.                     populateUnitSelectForRow(uuid, units, defaultUnitId);
  1829.                 }).fail(function() {
  1830.                     console.log('Failed to load measurement units for product:', productId);
  1831.                     measurementUnitsCache[productId] = []; // Cache empty array to prevent repeated requests
  1832.                 });
  1833.             }
  1834.             // Populate unit select dropdown for a specific row
  1835.             function populateUnitSelectForRow(uuid, units, defaultUnitId = null) {
  1836.                 const $select = $(`.quantity-unit-select[data-uuid="${uuid}"]`);
  1837.                 $select.empty();
  1838.                 if (Array.isArray(units) && units.length > 0) {
  1839.                     units.forEach(function(unit) {
  1840.                         const isSelected = defaultUnitId !== null && defaultUnitId !== undefined && defaultUnitId !== '' && String(unit.id) === String(defaultUnitId);
  1841.                         const selectedAttr = isSelected ? ' selected' : '';
  1842.                         $select.append('<option value="' + unit.id + '"' + selectedAttr + '>' + unit.symbol + '</option>');
  1843.                     });
  1844.                     if (units.length === 1) {
  1845.                         $select.prop('disabled', true);
  1846.                     } else {
  1847.                         $select.prop('disabled', false);
  1848.                     }
  1849.                 } else {
  1850.                     $select.append('<option value="">-</option>');
  1851.                     $select.prop('disabled', false);
  1852.                 }
  1853.             }
  1854.             // Update total amounts in footer
  1855.             function updateTotalAmounts() {
  1856.                 const taxLabelPrefix = "TVA";
  1857.                 const taxTotalLabel = "{{ 'total'|trans|e('js') }}";
  1858.                 const formatTaxRate = function(rateDecimal) {
  1859.                     const rateNumber = convertToDecimal(rateDecimal || 0).toNumber();
  1860.                     if (!isFinite(rateNumber)) {
  1861.                         return "0";
  1862.                     }
  1863.                     if (Math.abs(rateNumber % 1) < 1e-6) {
  1864.                         return rateNumber.toFixed(0);
  1865.                     }
  1866.                     return rateNumber.toFixed(2).replace(/\.?0+$/, "");
  1867.                 };
  1868.                 const totals = aggregateSoldTotals();
  1869.                 const $tfoot = $("#product-list-table tfoot");
  1870.                 $tfoot.empty();
  1871.                 // Helper to create row
  1872.                 const addRow = (label, value, isBold=false, colorClass='') => {
  1873.                     const row = $(`<tr class="summary-row ${isBold ? 'font-weight-bold' : ''} ${colorClass}">
  1874.                         <td colspan="8">
  1875.                             <div class="sale-summary-row">
  1876.                                 <span class="summary-label">${label}</span>
  1877.                                 <span class="summary-value">${formatCurrency(value)}</span>
  1878.                             </div>
  1879.                         </td>
  1880.                     </tr>`);
  1881.                     $tfoot.append(row);
  1882.                 };
  1883.                 // 1. Total H.T. (Gross)
  1884.                 addRow('Total H.T.:', totals.grossTotal, true);
  1885.                 // 2. Remise
  1886.                 if (!totals.totalDiscount.isZero() && totals.totalDiscount.toNumber() > 0.005) {
  1887.                      const $row = $(`<tr class="summary-row">
  1888.                         <td colspan="8">
  1889.                             <div class="sale-summary-row">
  1890.                                 <span class="summary-label">Remise:</span>
  1891.                                 <span class="summary-value">${formatCurrency(totals.totalDiscount.negated())}</span>
  1892.                             </div>
  1893.                         </td>
  1894.                     </tr>`);
  1895.                     $tfoot.append($row);
  1896.                 }
  1897.                 // 3. Total H.T. (Net)
  1898.                 addRow('Total H.T.:', totals.subtotal, true);
  1899.                 // 4. AVOIR
  1900.                 const totalVoucherAmount = calculateTotalGiftVoucherAmount();
  1901.                 let finalTotal = totals.grandTotalRounded; // Assuming totals.grandTotalRounded is available in result? Yes
  1902.                 // Wait, aggregateSoldTotals returns grandTotalRounded.
  1903.                 // But calculateSaleSummary in edit.html.twig returned summary.roundedTotal.
  1904.                 
  1905.                 if (!totalVoucherAmount.isZero()) {
  1906.                     const $avoirRow = $(`<tr class="summary-row text-danger font-weight-bold">
  1907.                         <td colspan="8">
  1908.                             <div class="sale-summary-row">
  1909.                                 <span class="summary-label">AVOIR:</span>
  1910.                                 <span class="summary-value">-${formatCurrency(totalVoucherAmount)}</span>
  1911.                             </div>
  1912.                         </td>
  1913.                     </tr>`);
  1914.                     $tfoot.append($avoirRow);
  1915.                     
  1916.                     finalTotal = finalTotal.minus(totalVoucherAmount);
  1917.                 }
  1918.                 // 5. TVA
  1919.                 const taxEntries = Object.values(totals.byTaxRate)
  1920.                     .filter(function(entry) { return !entry.amount.isZero(); })
  1921.                     .sort(function(a, b) { return a.rate.toNumber() - b.rate.toNumber(); });
  1922.                 if (taxEntries.length) {
  1923.                     taxEntries.forEach(function(entry) {
  1924.                          addRow(taxLabelPrefix + " (" + formatTaxRate(entry.rate) + "%):", entry.amount);
  1925.                     });
  1926.                 } else {
  1927.                      addRow(taxLabelPrefix + " (0%):", 0);
  1928.                 }
  1929.                 // 6. TOTAL
  1930.                 const $totalRow = $(`<tr class="total-row summary-row font-weight-bold">
  1931.                     <td colspan="8">
  1932.                         <div class="sale-summary-row summary-total">
  1933.                             <span class="summary-label">TOTAL À PAYER (EUR):</span>
  1934.                             <span class="summary-value display-5">${formatCurrency(finalTotal)}</span>
  1935.                         </div>
  1936.                     </td>
  1937.                 </tr>`);
  1938.                 $tfoot.append($totalRow);
  1939.                 calculateAndWriteDiffBetweenPaymentAndSolds();
  1940.             }
  1941.             // Update sold list item by UUID
  1942.             function updateSoldListItem(uuid, field, value) {
  1943.                 const itemIndex = soldList.findIndex(item => item.uuid === uuid);
  1944.                 if (itemIndex === -1) {
  1945.                     return;
  1946.                 }
  1947.                 soldList[itemIndex][field] = convertToDecimal(value).toNumber();
  1948.                 updateSoldProductFromRow(uuid);
  1949.             }
  1950.             // Satıştaki miktarları listeden düşme Un Allocated HARİÇ tutulur
  1951.             function fetchStocks() {
  1952.                 // soldList'i productid'e göre grupla
  1953.                 const groupedSoldList = soldList.reduce((acc, soldProduct) => {
  1954.                     if (!acc[soldProduct.productid]) {
  1955.                         acc[soldProduct.productid] = 0;
  1956.                     }
  1957.                     acc[soldProduct.productid] += convertToDecimal(soldProduct.baseUnitQuantity).toNumber();
  1958.                     return acc;
  1959.                 }, {});
  1960.                 // products listesindeki totalQuantity'yi güncelle
  1961.                 products.forEach(product => {
  1962.                     if (groupedSoldList[product.productid]) {
  1963.                         const totalSoldQuantity = groupedSoldList[product.productid];
  1964.                         product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity - totalSoldQuantity).toNumber());
  1965.                     } else {
  1966.                         // Eğer soldList'te bu ürün yoksa, quantity aynı kalır
  1967.                         product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity).toNumber());
  1968.                     }
  1969.                 });
  1970.                 calculateAndWriteDiffBetweenPaymentAndSolds();
  1971.             }
  1972.             function getTotalSoldAmount() {
  1973.                 return aggregateSoldTotals().grandTotalRounded.toNumber();
  1974.             }
  1975.             $(document).on('blur', '#payment-list-table-tbody .payment-amount-input', function() {
  1976.                 // Buraya kod eklenebilir
  1977.             });
  1978.             // Gelen değeri decimale çevirme
  1979.             function convertToDecimal(value) {
  1980.                 try {
  1981.                     if (value instanceof Decimal) {
  1982.                         return value;
  1983.                     }
  1984.                     if (value === undefined || value === null) {
  1985.                         return new Decimal(0);
  1986.                     }
  1987.                     if (typeof value === 'number') {
  1988.                         if (!isFinite(value) || isNaN(value)) {
  1989.                             return new Decimal(0);
  1990.                         }
  1991.                         return new Decimal(value);
  1992.                     }
  1993.                     if (typeof value === 'string') {
  1994.                         // Keep digits, minus, dot and comma; strip others
  1995.                         var sanitized = value.replace(/[^0-9.,-]/g, '').replace(/,/g, '').trim();
  1996.                         if (sanitized === '' || sanitized === '-' || sanitized === '.' || sanitized === '-.') {
  1997.                             return new Decimal(0);
  1998.                         }
  1999.                         return new Decimal(sanitized);
  2000.                     }
  2001.                     return new Decimal(0);
  2002.                 } catch (e) {
  2003.                     return new Decimal(0);
  2004.                 }
  2005.             }
  2006.             // Seçilen ürünün birimini dropdown'da gösterme (handled by main product select handler)
  2007.             function clearUnitSelects(){
  2008.                 $('#adder-quantity-unit-select').empty().append('<option value="">-</option>');
  2009.                 $('#adder-unallocated-quantity-unit-select').empty().append('<option value="">-</option>');
  2010.                 $('#modal-unallocated-unit').empty().append('<option value="">-</option>');
  2011.             }
  2012.             // Select içine çevrilebilir birimleri koyuyor
  2013.             function populateSelectWithUnits($select, units) {
  2014.                 $select.empty();
  2015.                 if (Array.isArray(units) && units.length > 0) {
  2016.                     units.forEach(function(unit) {
  2017.                         $select.append('<option value="' + unit.id + '">' + unit.symbol + '</option>');
  2018.                     });
  2019.                     if (units.length === 1) {
  2020.                         $select.prop('disabled', true);
  2021.                     } else {
  2022.                         $select.prop('disabled', false);
  2023.                     }
  2024.                 } else {
  2025.                     $select.append('<option value="">-</option>');
  2026.                     $select.prop('disabled', false);
  2027.                 }
  2028.             }
  2029.             // Ürünün çevrilebilir birimlerini select içine koyuyoruz.
  2030.             function refreshUnitSelectsForProduct(product) {
  2031.                 if (!product || !product.convertibleUnits) {
  2032.                     clearUnitSelects();
  2033.                     return;
  2034.                 }
  2035.                 populateSelectWithUnits($('#adder-quantity-unit-select'), product.convertibleUnits);
  2036.                 populateSelectWithUnits($('#adder-unallocated-quantity-unit-select'), product.convertibleUnits);
  2037.                 populateSelectWithUnits($('#modal-unallocated-unit'), product.convertibleUnits);
  2038.             }
  2039.             // TODO:  Birimler doldurulurken ilgili birimin karşılığındaki miktar yazılacak
  2040.             function fillUnitSelects(product){
  2041.                 // Check cache first
  2042.                 if (measurementUnitsCache[product.productid]) {
  2043.                     product.convertibleUnits = measurementUnitsCache[product.productid];
  2044.                     refreshUnitSelectsForProduct(product);
  2045.                     computeAndShowBaseUnitEquivalent();
  2046.                     return;
  2047.                 }
  2048.                 $.get('/product/' + product.productid + '/measurement-units', function(units) {
  2049.                     // Cache the units
  2050.                     measurementUnitsCache[product.productid] = units || [];
  2051.                     // Store in product object
  2052.                     product.convertibleUnits = units || [];
  2053.                     // Update selects
  2054.                     refreshUnitSelectsForProduct(product);
  2055.                     // Set default unit as selected
  2056.                     if (product.measurementUnitId) {
  2057.                         $('#adder-quantity-unit-select').val(product.measurementUnitId);
  2058.                         $('#adder-unallocated-quantity-unit-select').val(product.measurementUnitId);
  2059.                         $('#modal-unallocated-unit').val(product.measurementUnitId);
  2060.                     }
  2061.                     computeAndShowBaseUnitEquivalent();
  2062.                 });
  2063.             }
  2064.             // Seçilen birimde girilen miktarın, ürünün KENDİ birimindeki karşılığını hesaplar ve inputun altına küçük yazıyla gösterir
  2065.             function computeAndShowBaseUnitEquivalent() {
  2066.                 try {
  2067.                     const selectedProduct = getSelectedProduct();
  2068.                     if (!selectedProduct || !selectedProduct.productid) {
  2069.                         updateAllocatedQuantityHint('');
  2070.                         return;
  2071.                     }
  2072.                     const fromUnitId = $('#adder-quantity-unit-select').val();
  2073.                     const qtyStr = $('#adder-allocated-quantity').val();
  2074.                     // If base unit is selected, do not show duplicate label
  2075.                     if (fromUnitId && String(fromUnitId) === String(selectedProduct.measurementUnitId)) {
  2076.                         updateAllocatedQuantityHint('');
  2077.                         return;
  2078.                     }
  2079.                     if (!fromUnitId || fromUnitId === '' || qtyStr === '' || qtyStr === null) {
  2080.                         updateAllocatedQuantityHint('');
  2081.                         return;
  2082.                     }
  2083.                     const quantity = convertToDecimal(qtyStr).toNumber();
  2084.                     if (isNaN(quantity)) {
  2085.                         updateAllocatedQuantityHint('');
  2086.                         return;
  2087.                     }
  2088.                     $.ajax({
  2089.                         url: '/admin/measurement/convert-to-product-unit',
  2090.                         method: 'POST',
  2091.                         dataType: 'json',
  2092.                         data: {
  2093.                             productId: selectedProduct.productid,
  2094.                             fromUnitId: fromUnitId,
  2095.                             quantity: quantity
  2096.                         },
  2097.                         success: function(resp) {
  2098.                             if (resp && resp.success) {
  2099.                                 const unitText = selectedProduct.measurementUnit ? (' ' + selectedProduct.measurementUnit) : '';
  2100.                                 updateAllocatedQuantityHint((Number(resp.result).toFixed(2)) + unitText);
  2101.                             } else {
  2102.                                 updateAllocatedQuantityHint('');
  2103.                             }
  2104.                         },
  2105.                         error: function() {
  2106.                             updateAllocatedQuantityHint('');
  2107.                         }
  2108.                     });
  2109.                 } catch (e) {
  2110.                     updateAllocatedQuantityHint('');
  2111.                 }
  2112.             }
  2113.             // Base unit tipinden miktar.
  2114.             function updateAllocatedQuantityHint(text) {
  2115.                 const $group = $('#adder-allocated-quantity').closest('.input-group');
  2116.                 if ($('#allocated-quantity-converted-hint').length === 0) {
  2117.                     $('<small id="allocated-quantity-converted-hint" class="form-text text-muted"></small>').insertAfter('#adder-quantity-unit-select');
  2118.                 }
  2119.                 $('#allocated-quantity-converted-hint').text(text || '');
  2120.             }
  2121.             function roundToDecimal(value, decimalPlaces = 2) {
  2122.                 const decimalValue = new Decimal(value);
  2123.                 const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_UP);
  2124.                 return roundedValue.toNumber();
  2125.             }
  2126.             function roundStockToDecimal(value, decimalPlaces = 2) {
  2127.                 const decimalValue = new Decimal(value);
  2128.                 const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_DOWN);
  2129.                 return roundedValue.toNumber();
  2130.             }
  2131.             function selectProduct(productId) {
  2132.                 products.forEach(product => product.selected = false);
  2133.                 let product = products.find(product => product.productid === productId);
  2134.                 if (product) {
  2135.                     product.selected = true;
  2136.                 }
  2137.                 return products;
  2138.             }
  2139.             function getSelectedProduct() {
  2140.                 return products.find(product => product.selected === true);
  2141.             }
  2142.             // Ürün seçme selectini yeniden güncelleme.
  2143.             function updateProductSelect() {
  2144.                 $('#productselect').empty();
  2145.                 $('#productselect').append('<option value="0" data-image="image-not-found.webp">Ürün Seçin</option>');
  2146.                 $.each(products, function(index, value) {
  2147.                     var image = value.image;
  2148.                     var option = '<option value="' + value.id + '" data-image="' + image + '" class="stockoption">' + value.name + ' - ' + value.code + ' - ' + value.measurement + ' - ' + value.quantity + '</option>';
  2149.                     $('#productselect').append(option);
  2150.                 });
  2151.                 $('#productselect').attr('disabled', false);
  2152.             }
  2153.             $('#products_sold_quantity').mask("##0.00", {
  2154.                 reverse: true
  2155.             });
  2156.             $('#adder-allocated-quantity').mask("##0.00", {
  2157.                 reverse: true
  2158.             });
  2159.             $('#adder-unallocated-quantity').mask("##0.00", {
  2160.                 reverse: true
  2161.             });
  2162.             $('#adder-price').mask("##0.00", {
  2163.                 reverse: true
  2164.             });
  2165.             $('#adder-total').mask("##0.00", {
  2166.                 reverse: true
  2167.             });
  2168.             $('#products_sold_unitPriceFob').mask("#,##0.00", {
  2169.                 reverse: true
  2170.             });
  2171.             $('#products_sold_unitPriceNavlun').mask("#,##0.00", {
  2172.                 reverse: true
  2173.             });
  2174.             $('#products_sold_totalUnitPrice').mask("#,##0.00", {
  2175.                 reverse: true
  2176.             });
  2177.             $('#products_sold_totalUnitPurchasePrice').mask("#,##0.00", {
  2178.                 reverse: true
  2179.             });
  2180.             $('#products_sold_totalPrice').mask("#,##0.00", {
  2181.                 reverse: true
  2182.             });
  2183.             $('#products_sold_totalPuchasePrice').mask("#,##0.00", {
  2184.                 reverse: true
  2185.             });
  2186.             $('#sales_form_totalPurchasePrice').mask("#,##0.00", {
  2187.                 reverse: true
  2188.             });
  2189.             $('#sales_form_totalPurchasePrice').attr('readonly', 'readonly');
  2190.             var totalQuantity = 0;
  2191.             var productId = 0;
  2192.             var productname = $('#products_sold_productName');
  2193.             var productmeasurement = $('#products_sold_measurement');
  2194.             var unitPriceFob = $('#products_sold_unitPriceFob');
  2195.             var unitPricenavlun = $('#products_sold_unitPriceNavlun');
  2196.             var totalUnitPrice = $('#products_sold_totalUnitPrice');
  2197.             // ÖDEME FORMU LİSTEYE EKLEME
  2198.             $('#save-payment-btn').on('click', function() {
  2199.                 let amount = roundToDecimal(convertToDecimal($('#payment_amount').val()));
  2200.                 let payment_paymentStatus = $('#payment_paymentStatus').val();
  2201.                 let payment_paymentDate = $('#payment_paymentDate').val();
  2202.                 let payment_dueDate = $('#payment_dueDate').val();
  2203.                 let payment_paymentMethod = $('#payment_paymentMethod').val();
  2204.                 let payment_description = $('#payment_description').val();
  2205.                 if (amount <= 0 || !payment_paymentStatus || !payment_paymentDate || !payment_dueDate || !payment_paymentMethod || !payment_description) {
  2206.                     throw new Error("Formdaki eksikleri tamamlayın lütfen")
  2207.                 }
  2208.                 payment = {
  2209.                     uuid: crypto.randomUUID(),
  2210.                     amount: amount,
  2211.                     status: payment_paymentStatus,
  2212.                     paymentDate: payment_paymentDate,
  2213.                     paymentDueDate: payment_dueDate,
  2214.                     paymentMethod: payment_paymentMethod,
  2215.                     description: payment_description,
  2216.                     voucherCode: $('#payment_voucherCode').val()
  2217.                 }
  2218.                 payments.push(payment);
  2219.                 fetchPaymentForm();
  2220.                 // Trigger update to show AVOIR if needed
  2221.                 fetchSoldListRows();
  2222.             });
  2223.             // Ödeme satırını silme işlemi
  2224.             $(document).on('click', '.delete-payment', function() {
  2225.                 $row = $(this).closest('tr');
  2226.                 let id = $row.attr("id");
  2227.                 payments = payments.filter(payment => payment.uuid !== id);
  2228.                 fetchPaymentForm();
  2229.                 // Trigger update to remove AVOIR if needed
  2230.                 fetchSoldListRows();
  2231.             });
  2232.             // Ödeme formunu güncelleme
  2233.             function fetchPaymentForm() {
  2234.                 $('#payment-list-table-tbody').empty();
  2235.                 payments.forEach(function(val) {
  2236.                     var newRow = `<tr id="${val.uuid}">
  2237.                         <td><input type="text" class="form-control payment-amount payment-amount-input" value="${addCommas(val.amount) || ''}" name="paymentAmount[]" placeholder="Miktar"></td>
  2238.                         <td>
  2239.                             <select class="form-control" name="paymentStatus[]">
  2240.                                 <option value="0" ${val.status === "0" ? 'selected' : ''}>{% trans %} payment_status.paid {% endtrans %}</option>
  2241.                                 <option value="1" ${val.status === "1" ? 'selected' : ''}>{% trans %} payment_status.not_paid {% endtrans %}</option>
  2242.                                 <option value="2" ${val.status === "2" ? 'selected' : ''}>{% trans %} payment_status.term_sale {% endtrans %}</option>
  2243.                             </select>
  2244.                         </td>
  2245.                         <td><input name="paymentDate[]" type="date" class="form-control" value="${val.paymentDate || ''}" required></td>
  2246.                         <td><input name="paymentDueDate[]" type="date" class="form-control" value="${val.paymentDueDate || ''}" required></td>
  2247.                         <td>
  2248.                             <select class="form-control" name="paymentMethod[]">
  2249.                                 <option value="0">Ödeme Yöntemi Seçin</option>
  2250.                                 {% for paymentMethod in paymentMethods %}
  2251.                                 <option value="{{ paymentMethod.id }}" ${val.paymentMethod === '{{ paymentMethod.id }}' ? 'selected' : ''}>
  2252.                                     {{ paymentMethod.name }}
  2253.                                 </option>
  2254.                                 {% endfor %}
  2255.                             </select>
  2256.                         </td>
  2257.                         <td><input type="text" class="form-control" value="${val.description || ''}" name="description[]">
  2258.                             <input type="hidden" name="voucherCode[]" value="${val.voucherCode || ''}">
  2259.                         </td>
  2260.                         <td><button type="button" class="btn btn-primary btn-sm save-payment-row">{% trans %} save {% endtrans %}</button></td>
  2261.                         <td><button type="button" class="btn btn-danger btn-sm delete-payment">{% trans %} delete {% endtrans %}</button></td>
  2262.                     </tr>`;
  2263.                     // Yeni satırı tabloya ekle
  2264.                     $('#payment-list-table-tbody').append(newRow);
  2265.                     $('#add-payment-modal').modal("hide");
  2266.                     resetPaymentForm();
  2267.                 });
  2268.                 calculateAndWriteDiffBetweenPaymentAndSolds();
  2269.             }
  2270.             function updatePaymentByUUID(uuid, updatedData) {
  2271.                 const paymentIndex = payments.findIndex(payment => payment.uuid === uuid);
  2272.                 if (paymentIndex === -1) {
  2273.                     console.error(`Payment with UUID ${uuid} not found!`);
  2274.                     return false;
  2275.                 }
  2276.                 payments[paymentIndex] = {
  2277.                     ...payments[paymentIndex], // Mevcut veriler
  2278.                     ...updatedData // Yeni veriler
  2279.                 };
  2280.                 fetchPaymentForm();
  2281.                 return true;
  2282.             }
  2283.             function calculateAndWriteDiffBetweenPaymentAndSolds(existingTotals = null) {
  2284.                 const totals = existingTotals || aggregateSoldTotals();
  2285.                 const saleTotalRounded = totals.grandTotalRounded;
  2286.                 const paymentTotal = convertToDecimal(calculateTotalPaymentAmount());
  2287.                 const diff = totals.grandTotal.minus(paymentTotal);
  2288.                 const diffRounded = diff.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
  2289.                 $('#total-amount-span').html(addCommas(saleTotalRounded.toFixed(2)));
  2290.                 $('#remainder-amount-span').html(addCommas(diffRounded.toFixed(2)));
  2291.             }
  2292.             function resetPaymentForm() {
  2293.                 $('#payment_amount').val('');
  2294.                 $('#payment_description').val('');
  2295.                 $('#payment_voucherCode').val('');
  2296.                 $('#payment_voucherCode').closest('.form-group').hide(); // Reset visibility
  2297.             }
  2298.             $(document).on('click', '.save-payment-row', function() {
  2299.                 const $row = $(this).closest('tr');
  2300.                 const uuid = $row.attr('id');
  2301.                 const updatedData = {
  2302.                     amount: convertToDecimal($row.find('input[name="paymentAmount[]"]').val().replace(',', '')).toNumber(),
  2303.                     status: $row.find('select[name="paymentStatus[]"]').val(),
  2304.                     paymentDate: $row.find('input[name="paymentDate[]"]').val(),
  2305.                     paymentDueDate: $row.find('input[name="paymentDueDate[]"]').val(),
  2306.                     paymentMethod: $row.find('select[name="paymentMethod[]"]').val(),
  2307.                     description: $row.find('input[name="description[]"]').val(),
  2308.                     voucherCode: $row.find('input[name="voucherCode[]"]').val()
  2309.                 };
  2310.                 const result = updatePaymentByUUID(uuid, updatedData);
  2311.                 if (result) {
  2312.                     showSuccessAlert("Ödeme satırı güncellendi :", updatedData);
  2313.                 } else {
  2314.                     console.log("Failed to update payment.");
  2315.                 }
  2316.             });
  2317.             function resetAddProductForm() {
  2318.                 $('#products_sold_quantity').val(0);
  2319.                 $('#products_sold_totalUnitPurchasePrice').val(0);
  2320.                 $('#products_sold_unAllocatedQuantity').val(0);
  2321.                 $('#products_sold_totalPuchasePrice').val(0);
  2322.             }
  2323.             function calculatePrice() {
  2324.                 var quantityP = convertToFloat($('#products_sold_quantity').val());
  2325.                 var unAllocatedQuantityP = convertToFloat($('#products_sold_unAllocatedQuantity').val());
  2326.                 var totalUnitPurchasePriceP = convertToFloat($('#products_sold_totalUnitPurchasePrice').val());
  2327.                 var taxP = convertToFloat($('#products_sold_tax').val());
  2328.                 var discountP = convertToFloat($('#products_sold_discount').val());
  2329.                 var totalAmount = (quantityP + unAllocatedQuantityP) * totalUnitPurchasePriceP;
  2330.                 var discountedAmount = totalAmount - (totalAmount * (discountP / 100));
  2331.                 var finalAmount = discountedAmount + (discountedAmount * (taxP / 100));
  2332.                 finalAmount = roundToTwoDecimals(finalAmount);
  2333.                 $('#products_sold_totalPuchasePrice').val(finalAmount);
  2334.             }
  2335.             $(document).on('change', '#products_sold_tax', calculatePrice);
  2336.             $(document).on('change', '#products_sold_discount', calculatePrice);
  2337.             $(document).on('change', '#products_sold_quantity', calculatePrice);
  2338.             $(document).on('change', '#products_sold_totalUnitPurchasePrice', calculatePrice);
  2339.             function roundToTwoDecimals(number) {
  2340.                 var factor = Math.pow(10, 2);
  2341.                 return (Math.round(number * factor) / factor).toFixed(2);
  2342.             }
  2343.             function validate(s) {
  2344.                 var rgx = /^[0-9]*\.?[0-9]*$/;
  2345.                 return s.match(rgx);
  2346.             }
  2347.             function convertToFloat(value) {
  2348.                 if (typeof value === "string") {
  2349.                     value = value.replace(',', '');
  2350.                 }
  2351.                 return parseFloat(value);
  2352.             }
  2353.             $(document).on('change', 'input[name="totalUnitPurchasePrice[]"]', function() {
  2354.                 var unitPrice = $(this).val();
  2355.             });
  2356.             var productCode = 0;
  2357.             $('#productselect').on('change', function() {
  2358.                 $.ajax({
  2359.                     url: "/stock-detail/" + $('#productselect option:selected').val(),
  2360.                     dataType: 'JSON',
  2361.                     method: 'GET',
  2362.                     success: function(response) {
  2363.                         var productid = response.productid;
  2364.                         var product = products.find(function(item) {
  2365.                             return item.productid === productid;
  2366.                         });
  2367.                         product.length = response.length;
  2368.                         product.priceFob = response.priceFob;
  2369.                         product.priceNavlun = response.priceNavlun;
  2370.                         product.totalQuantity = response.totalQuantity;
  2371.                         product.quantity = response.quantity;
  2372.                         product.unAllocatedQuantity = response.unAllocatedQuantity;
  2373.                         product.thickness = response.thickness;
  2374.                         product.totalUnitPrice = response.totalUnitPrice;
  2375.                         product.width = response.width;
  2376.                         selectProduct(product.productid);
  2377.                         getProductCost(product.productid);
  2378.                         fetchStocks();
  2379.                         productname.val(product.name);
  2380.                         productmeasurement.val(product.measurement);
  2381.                         productCode = product.code;
  2382.                         unitPriceFob.val(addCommas(product.priceFob));
  2383.                         unitPricenavlun.val(addCommas(product.priceNavlun));
  2384.                         totalUnitPrice.val(addCommas(product.priceNavlun + response.priceFob));
  2385.                         totalQuantity = product.totalQuantity;
  2386.                         productId = product.productid;
  2387.                         // Seçilen ürün için birimleri yükle ve ana birimi seç
  2388.                         fillUnitSelects(product);
  2389.                         computeAndShowBaseUnitEquivalent();
  2390.                     },
  2391.                     error: function(error) {
  2392.                         // Hata yönetimi
  2393.                     }
  2394.                 });
  2395.             });
  2396.             function isProductService(productId){
  2397.                 const product = products.find(product => product.productid === productId);
  2398.                 const productType = product.productType.key;
  2399.                 if(productType === "INSTALLATION_SERVICE" || productType === "MAINTENANCE_SERVICE" || productType === "CONSULTATION_SERVICE" || productType === 'TREE'){
  2400.                     return true;
  2401.                 }
  2402.                 return false;
  2403.             }
  2404.             // TODO
  2405.             $('#add-product-row-btn').on('click', function() {
  2406.                 var productId = $('#productselect').val();
  2407.                 if (!productId || productId <= 0) {
  2408.                     showErrorAlert("Ürün seçimi yapın");
  2409.                     return;
  2410.                 }
  2411.                 let product = getSelectedProduct();
  2412.                 const productType = product.productType.key;
  2413.                 var quantity = $('#adder-allocated-quantity').val();
  2414.                 var unAllocatedQuantity = $('#adder-unallocated-quantity').val();
  2415.                 var unitPrice = $('#adder-price').val();
  2416.                 var tax = $('#adder-tva-select').val();
  2417.                 var discount = $('#adder-discount-select').val();
  2418.                 var totalPurchasePrice = $('#adder-total').val();
  2419.                 quantity = convertToDecimal(quantity);
  2420.                 unAllocatedQuantity = convertToDecimal(unAllocatedQuantity);
  2421.                 unitPrice = convertToDecimal(unitPrice);
  2422.                 tax = convertToDecimal(tax);
  2423.                 discount = convertToDecimal(discount);
  2424.                 totalPurchasePrice = convertToDecimal(totalPurchasePrice);
  2425.                 if(productType === 'TREE') {
  2426.                 }
  2427.                 checkStockQuantity(quantity);
  2428.                 const selectedUnitId = $('#adder-quantity-unit-select').val();
  2429.                 if (!selectedUnitId || String(selectedUnitId) === '') {
  2430.                     showErrorAlert('Lütfen birim seçiniz.');
  2431.                     return;
  2432.                 }
  2433.                 const item =  addProductToSoldList(
  2434.                                     product.productid,
  2435.                                     product.name,
  2436.                                     product.code,
  2437.                                     product.measurement,
  2438.                                     quantity,
  2439.                                     unitPrice,
  2440.                                     tax,
  2441.                                     discount,
  2442.                                     totalPurchasePrice,
  2443.                                     product.warehouseid,
  2444.                                     unAllocatedQuantity,
  2445.                                     product.thickness,
  2446.                                     product.width,
  2447.                                     product.length,
  2448.                                     product.cost,
  2449.                                     product.productType,
  2450.                                     selectedUnitId,
  2451.                                 );
  2452.                 if(isProductService(product.productid)){
  2453.                     $('#update_service_id').val(item.uuid);
  2454.                     $('#updateServiceModal').modal('show');
  2455.                 }else{
  2456.                   if(quantity.toNumber() === 0 && unAllocatedQuantity.toNumber() === 0) {
  2457.                     showErrorAlert("Miktar veya tahsis edilmemiş miktar 0 olamaz");
  2458.                     return;
  2459.                   }
  2460.                 }
  2461.                 fetchSoldListRows();
  2462.                 resetAdderRow();
  2463.             });
  2464.             $('#service_modal_save_changes_btn').on('click', function() {
  2465.                 const uuid = $('#update_service_id').val();
  2466.                 const soldProduct = soldList.find(soldProduct => soldProduct.uuid === uuid);
  2467.                 const product  = products.find(product => product.productid === soldProduct.productid);
  2468.                 const productType = product.productType.key;
  2469.                 // Modal değerlerini geri alıp objeye yazıyoruz
  2470.                 soldProduct.productName = $('#update_service_name').val();
  2471.                 soldProduct.cost = parseFloat($('#update_service_cost').val());
  2472.                 soldProduct.description = $('#update_service_description').val();
  2473.                 const tr = $('#' + uuid);
  2474.                 console.log("Servis güncellendi:", soldProduct);
  2475.                 $('#updateServiceModal').modal('hide');
  2476.                 // Rows'ları yeniden render et ki service adı dropdown'da görünsün
  2477.                 fetchSoldListRows();
  2478.                 updateTotalAmounts();
  2479.                 if(isProductService(product.productid)){
  2480.                     addServiceEditButtonIfNeeded(uuid);
  2481.                 }
  2482.             });
  2483.             $(document).on('click', '.edit-service-button', function () {
  2484.                 const uuid = $(this).data('uuid');
  2485.                 const soldProduct = soldList.find(sp => sp.uuid === uuid);
  2486.                 $('#update_service_id').val(uuid);
  2487.                 $('#update_service_name').val(soldProduct?.productName || "");
  2488.                 $('#update_service_cost').val(soldProduct?.cost || "0.00");
  2489.                 $('#update_service_description').val(soldProduct?.description || "");
  2490.                 $('#updateServiceModal').modal('show');
  2491.             });
  2492.             // TODO:
  2493.             function addServiceEditButtonIfNeeded(uuid) {
  2494.                 const soldProduct = soldList.find(sp => sp.uuid === uuid);
  2495.                 const product = products.find(p => p.productid === soldProduct.productid);
  2496.                 const productType = product.productType.key;
  2497.                 // Sadece ürün service ise buton ekle
  2498.                 if (product && isProductService(product.productid)) {
  2499.                     const tr = $('#' + uuid);
  2500.                     // Eğer daha önce eklenmediyse ekle
  2501.                     if (tr.find('.edit-service-button').length === 0) {
  2502.                         const btn = `
  2503.                             <button type="button"
  2504.                                     class="btn btn-warning btn-sm edit-service-button"
  2505.                                     data-uuid="${uuid}"
  2506.                                     title="Servisi Güncelle">
  2507.                                 <i class="fas fa-edit"></i>
  2508.                             </button>`;
  2509.                         // örnek: sil butonunun yanına ekleyelim
  2510.                         tr.find('.btn-group').prepend(btn);
  2511.                     }
  2512.                 }
  2513.             }
  2514.             // Reset adder row form after adding product
  2515.             function resetAdderRow() {
  2516.                 $('#productselect').val('').trigger('change');
  2517.                 $('#adder-allocated-quantity').val('0');
  2518.                 $('#adder-unallocated-quantity').val('0');
  2519.                 $('#adder-price').val('0');
  2520.                 $('#adder-discount-select').val('0');
  2521.                 $('#adder-tva-select').val('0');
  2522.                 $('#adder-total').val('0');
  2523.                 $('#unalloc-display').text('0');
  2524.                 clearUnitSelects();
  2525.                 $('#info-details-content').hide();
  2526.                 $('#show-info-btn i').removeClass('fa-chevron-up').addClass('fa-info-circle');
  2527.             }
  2528.             // =============================================================================
  2529.             // EVENT HANDLERS FOR SALES LIST EDITING
  2530.             // =============================================================================
  2531.             // Show/hide product info details
  2532.             $(document).on('click', '.show-row-info-btn', function() {
  2533.                 const $infoContent = $(this).closest('tr').next('.info-details-row-class').find('.info-details-content-class');
  2534.                 const $icon = $(this).find('i');
  2535.                 if ($infoContent.is(':visible')) {
  2536.                     $infoContent.hide();
  2537.                     $icon.removeClass('fa-chevron-up').addClass('fa-info-circle');
  2538.                     $(this).closest('tr').next('.info-details-row-class').hide();
  2539.                 } else {
  2540.                     $infoContent.show();
  2541.                     $icon.removeClass('fa-info-circle').addClass('fa-chevron-up');
  2542.                     $(this).closest('tr').next('.info-details-row-class').show();
  2543.                 }
  2544.             });
  2545.             $(document).on('change', '.product-select-row', function() {
  2546.                 const uuid = $(this).data('uuid');
  2547.                 const newProductId = parseInt($(this).val());
  2548.                 const soldItemIndex = soldList.findIndex(item => item.uuid === uuid);
  2549.                 if (soldItemIndex > -1) {
  2550.                     const newProduct = products.find(p => p.productid === newProductId);
  2551.                     if (newProduct) {
  2552.                         soldList[soldItemIndex].productid = newProduct.productid;
  2553.                         soldList[soldItemIndex].productName = newProduct.name;
  2554.                         soldList[soldItemIndex].code = newProduct.code;
  2555.                         soldList[soldItemIndex].measurement = newProduct.measurement;
  2556.                         soldList[soldItemIndex].cost = newProduct.cost;
  2557.                     }
  2558.                     fetchSoldListRows();
  2559.                     updateTotalAmounts();
  2560.                 }
  2561.                 console.table(soldList);
  2562.             });
  2563.             // Handle unit price changes in sold list rows
  2564.             $(document).on('input change', '.price-input-row', function() {
  2565.                 const uuid = $(this).data('uuid');
  2566.                 const value = $(this).val();
  2567.                 // Birim fiyat manuel değiştirildi, artık otomatik hesaplama yapılmalı
  2568.                 delete rowHasManualTotal[uuid];
  2569.                 updateSoldListItem(uuid, 'unitPrice', value);
  2570.             });
  2571.             // Handle discount changes in sold list rows
  2572.             $(document).on('change', '.discount-select-row', function() {
  2573.                 const uuid = $(this).data('uuid');
  2574.                 const value = $(this).val();
  2575.                 // İndirim değiştirildi, artık otomatik hesaplama yapılmalı
  2576.                 delete rowHasManualTotal[uuid];
  2577.                 updateSoldListItem(uuid, 'discount', value);
  2578.             });
  2579.             // Handle tax changes in sold list rows
  2580.             $(document).on('change', '.tax-select-row', function() {
  2581.                 const uuid = $(this).data('uuid');
  2582.                 const value = $(this).val();
  2583.                 // KDV değiştirildi, artık otomatik hesaplama yapılmalı
  2584.                 delete rowHasManualTotal[uuid];
  2585.                 updateSoldListItem(uuid, 'tax', value);
  2586.             });
  2587.             // Handle unit selection changes in sold list rows
  2588.             $(document).on('change', '.quantity-unit-select', function() {
  2589.                 const uuid = $(this).data('uuid');
  2590.                 const unitId = $(this).val();
  2591.                 const $quantityInput = $(`.quantity-input-row[data-uuid="${uuid}"]`);
  2592.                 const currentQuantity = parseFloat($quantityInput.val()) || 0;
  2593.                 // Update the selected unit in sold list
  2594.                 const itemIndex = soldList.findIndex(item => item.uuid === uuid);
  2595.                 if (itemIndex !== -1) {
  2596.                     const item = soldList[itemIndex];
  2597.                     const oldUnitId = item.selectedUnitId;
  2598.                     item.selectedUnitId = unitId;
  2599.                     // Show unit conversion equivalent
  2600.                     showUnitConversionForRow(uuid, item.productid, currentQuantity, unitId);
  2601.                     // If changing from one unit to another, convert the quantity
  2602.                     if (oldUnitId && oldUnitId !== unitId && unitId && currentQuantity > 0) {
  2603.                         convertQuantityBetweenUnits(item.productid, currentQuantity, oldUnitId, unitId, function(convertedQuantity) {
  2604.                             if (convertedQuantity !== null) {
  2605.                                 $quantityInput.val(convertedQuantity.toFixed(2));
  2606.                                 updateSoldListItem(uuid, 'quantity', convertedQuantity);
  2607.                                 // Update conversion display with new quantity
  2608.                                 showUnitConversionForRow(uuid, item.productid, convertedQuantity, unitId);
  2609.                             }
  2610.                         });
  2611.                     }
  2612.                     // Recompute and store base unit quantity for the row
  2613.                     computeBaseUnitQuantity(item.productid, unitId, parseFloat($quantityInput.val()) || 0, function(baseQty){
  2614.                         const idx = soldList.findIndex(sp => sp.uuid === uuid);
  2615.                         if(idx !== -1){ soldList[idx].baseUnitQuantity = convertToDecimal(baseQty || 0).toNumber(); }
  2616.                     });
  2617.                 }
  2618.             });
  2619.             // Handle quantity changes in sold list rows to update conversion display
  2620.             $(document).on('input change', '.quantity-input-row', function() {
  2621.                 const uuid = $(this).data('uuid');
  2622.                 const value = $(this).val();
  2623.                 const item = soldList.find(item => item.uuid === uuid);
  2624.                 if (item && item.selectedUnitId) {
  2625.                     showUnitConversionForRow(uuid, item.productid, parseFloat(value) || 0, item.selectedUnitId);
  2626.                 }
  2627.                 // Miktar manuel değiştirildi, artık otomatik hesaplama yapılmalı
  2628.                 delete rowHasManualTotal[uuid];
  2629.                 updateSoldListItem(uuid, 'quantity', value);
  2630.                 // Also refresh baseUnitQuantity on quantity change
  2631.                 if (item) {
  2632.                     computeBaseUnitQuantity(item.productid, item.selectedUnitId, parseFloat(value) || 0, function(baseQty){
  2633.                         const idx = soldList.findIndex(sp => sp.uuid === uuid);
  2634.                         if(idx !== -1){ soldList[idx].baseUnitQuantity = convertToDecimal(baseQty || 0).toNumber(); }
  2635.                     });
  2636.                 }
  2637.             });
  2638.             // Show unit conversion equivalent for a specific row
  2639.             function showUnitConversionForRow(uuid, productId, quantity, fromUnitId) {
  2640.                 if (!fromUnitId || !quantity || quantity <= 0) {
  2641.                     updateRowQuantityHint(uuid, '');
  2642.                     return;
  2643.                 }
  2644.                 const product = products.find(p => p.productid === productId);
  2645.                 if (!product || !product.measurementUnitId) {
  2646.                     updateRowQuantityHint(uuid, '');
  2647.                     return;
  2648.                 }
  2649.                 // If already in product's main unit, no conversion needed
  2650.                 if (fromUnitId == product.measurementUnitId) {
  2651.                     updateRowQuantityHint(uuid, '');
  2652.                     return;
  2653.                 }
  2654.                 $.ajax({
  2655.                     url: '/admin/measurement/convert-to-product-unit',
  2656.                     method: 'POST',
  2657.                     dataType: 'json',
  2658.                     data: {
  2659.                         productId: productId,
  2660.                         fromUnitId: fromUnitId,
  2661.                         quantity: quantity
  2662.                     },
  2663.                     success: function(resp) {
  2664.                         if (resp && resp.success) {
  2665.                             const unitText = product.measurementUnit ? (' ' + product.measurementUnit) : '';
  2666.                             updateRowQuantityHint(uuid, (Number(resp.result).toFixed(2)) + unitText);
  2667.                 } else {
  2668.                             updateRowQuantityHint(uuid, '');
  2669.                         }
  2670.                     },
  2671.                     error: function() {
  2672.                         updateRowQuantityHint(uuid, '');
  2673.                     }
  2674.                 });
  2675.             }
  2676.             // Update quantity hint for a specific row
  2677.             function updateRowQuantityHint(uuid, text) {
  2678.                 let $hint = $(`.quantity-conversion-hint[data-uuid="${uuid}"]`);
  2679.                 if ($hint.length === 0) {
  2680.                     const $quantityCell = $(`.quantity-input-row[data-uuid="${uuid}"]`).closest('td');
  2681.                     $quantityCell.append(`<small class="form-text text-muted quantity-conversion-hint" data-uuid="${uuid}"></small>`);
  2682.                     $hint = $(`.quantity-conversion-hint[data-uuid="${uuid}"]`);
  2683.                 }
  2684.                 $hint.text(text || '');
  2685.             }
  2686.             // Convert quantity between different measurement units
  2687.             function convertQuantityBetweenUnits(productId, quantity, fromUnitId, toUnitId, callback) {
  2688.                 if (!fromUnitId || !toUnitId || fromUnitId === toUnitId) {
  2689.                     callback(quantity);
  2690.                     return;
  2691.                 }
  2692.                 $.ajax({
  2693.                     url: '/admin/measurement/convert-between-units',
  2694.                     method: 'POST',
  2695.                     dataType: 'json',
  2696.                     data: {
  2697.                         productId: productId,
  2698.                         fromUnitId: fromUnitId,
  2699.                         toUnitId: toUnitId,
  2700.                         quantity: quantity
  2701.                     },
  2702.                     success: function(resp) {
  2703.                         if (resp && resp.success) {
  2704.                             callback(parseFloat(resp.result));
  2705.                 } else {
  2706.                             console.error('Unit conversion failed:', resp);
  2707.                             callback(null);
  2708.                         }
  2709.                     },
  2710.                     error: function() {
  2711.                         console.error('Unit conversion request failed');
  2712.                         callback(null);
  2713.                     }
  2714.                 });
  2715.             }
  2716.             // Handle total price changes (reverse calculation to unit price)
  2717.             $(document).on('change blur', '.total-input-row', function() {
  2718.                 updateUnitPriceFromTotal($(this).data('uuid'));
  2719.             });
  2720.             // Handle delete row button clicks
  2721.             const currencySymbol = "{{ 'currency.symbol'|trans|e('js') }}";
  2722.             const PAYMENT_TOLERANCE = 0.01;
  2723.             const AUTO_PAYMENT_DESCRIPTION = 'Sistem tarafından ödeme otomatik oluşturuldu';
  2724.             const AUTO_PAYMENT_STATUS_TEXT = 'Ödendi';
  2725.             let autoPaymentModalState = null;
  2726.             let cachedCashPaymentMethod = null;
  2727.             function detectCashPaymentMethod() {
  2728.                 const $options = $('#payment_paymentMethod option');
  2729.                 let fallback = null;
  2730.                 let cashCandidate = null;
  2731.                 $options.each(function() {
  2732.                     const $option = $(this);
  2733.                     const value = ($option.val() || '').trim();
  2734.                     if (!value) {
  2735.                         return;
  2736.                     }
  2737.                     if (!fallback) {
  2738.                         fallback = { id: value, label: ($option.text() || '').trim() };
  2739.                     }
  2740.                     const label = ($option.text() || '').toLowerCase();
  2741.                     if (label.includes('cash') || label.includes('nakit')) {
  2742.                         cashCandidate = { id: value, label: ($option.text() || '').trim() };
  2743.                         return false;
  2744.                     }
  2745.                 });
  2746.                 if (cashCandidate) {
  2747.                     return cashCandidate;
  2748.                 }
  2749.                 if (fallback) {
  2750.                     return fallback;
  2751.                 }
  2752.                 return { id: '1', label: 'Cash' };
  2753.             }
  2754.             function ensureCashPaymentMethod() {
  2755.                 if (!cachedCashPaymentMethod) {
  2756.                     cachedCashPaymentMethod = detectCashPaymentMethod();
  2757.                 }
  2758.                 return cachedCashPaymentMethod;
  2759.             }
  2760.             function normalizeCurrency(value) {
  2761.                 return convertToDecimal(value).toDecimalPlaces(2, Decimal.ROUND_HALF_UP).toNumber();
  2762.             }
  2763.             function formatCurrency(value) {
  2764.                 const normalized = normalizeCurrency(value);
  2765.                 const formatted = addCommas(normalized.toFixed(2));
  2766.                 return formatted.replace(/,/g, '#').replace(/\./g, ',').replace(/#/g, '.') + ' ' + currencySymbol;
  2767.             }
  2768.             function calculateTotalPaymentAmount() {
  2769.                 let total = new Decimal(0);
  2770.                 payments.forEach(function(payment) {
  2771.                     total = total.plus(convertToDecimal(payment.amount));
  2772.                 });
  2773.                 return total.toNumber();
  2774.             }
  2775.             function getSaleDateValue() {
  2776.                 const saleDate = $('#sales_form_salesDate').val();
  2777.                 if (saleDate && saleDate.trim() !== '') {
  2778.                     return saleDate;
  2779.                 }
  2780.                 return new Date().toISOString().split('T')[0];
  2781.             }
  2782.             function createAutoPaymentPayload(amount, saleDate) {
  2783.                 const method = ensureCashPaymentMethod();
  2784.                 return {
  2785.                     uuid: crypto.randomUUID(),
  2786.                     amount: normalizeCurrency(amount),
  2787.                     status: '0',
  2788.                     paymentDate: saleDate,
  2789.                     paymentDueDate: saleDate,
  2790.                     paymentMethod: method.id,
  2791.                     description: AUTO_PAYMENT_DESCRIPTION
  2792.                 };
  2793.             }
  2794.             function openAutoPaymentModal(config) {
  2795.                 autoPaymentModalState = {
  2796.                     scenario: config.scenario,
  2797.                     amount: normalizeCurrency(config.amount),
  2798.                     saleDate: config.saleDate,
  2799.                     saleTotal: normalizeCurrency(config.saleTotal),
  2800.                     currentPayments: normalizeCurrency(config.currentPayments)
  2801.                 };
  2802.                 const method = ensureCashPaymentMethod();
  2803.                 let message = '';
  2804.                 if (config.scenario === 'no-payment') {
  2805.                     message = 'Bu satış için henüz ödeme girilmedi. Aşağıdaki bilgilerle otomatik bir nakit ödeme oluşturulacaktır.';
  2806.                 } else {
  2807.                     message = 'Girilen ödemeler satış toplamı ile uyumsuz. Eksik tutar otomatik bir nakit ödeme olarak eklenecektir.';
  2808.                 }
  2809.                 $('#auto-payment-message').text(message);
  2810.                 $('#auto-payment-sale-total').text(formatCurrency(autoPaymentModalState.saleTotal));
  2811.                 $('#auto-payment-current-payments').text(formatCurrency(autoPaymentModalState.currentPayments));
  2812.                 $('#auto-payment-amount').text(formatCurrency(autoPaymentModalState.amount));
  2813.                 $('#auto-payment-method').text(method.label);
  2814.                 $('#auto-payment-status').text(AUTO_PAYMENT_STATUS_TEXT);
  2815.                 $('#auto-payment-description').text(AUTO_PAYMENT_DESCRIPTION);
  2816.                 $('#confirm-auto-payment').prop('disabled', false);
  2817.                 $('#autoPaymentModal').modal('show');
  2818.             }
  2819.             function validateSalesForm() {
  2820.                 const form = $('form[name="sales_form"]');
  2821.                 if (!form || form.length === 0) {
  2822.                     return true;
  2823.                 }
  2824.                 return form[0].reportValidity();
  2825.             }
  2826.             function submitSalesRequest() {
  2827.                 if (!validateSalesForm()) {
  2828.                     return;
  2829.                 }
  2830.                 const salesData = {
  2831.                     payments: payments,
  2832.                     soldList: soldList,
  2833.                     invoice: {
  2834.                         invoiceNumber: $('#sales_form_invoiceNumber').val(),
  2835.                         customer: $('#sales_form_customer').val(),
  2836.                         paymentStatus: $('#sales_form_paymentStatus').val(),
  2837.                         salesDate: $('#sales_form_salesDate').val(),
  2838.                         deliveryDate: $('#sales_form_deliveryDate').val(),
  2839.                         dueDate: $('#sales_form_dueDate').val(),
  2840.                         validDate: $('#sales_form_validDate').val(),
  2841.                         seller: $('#sales_form_seller').val(),
  2842.                         description: $('#sales_form_description').val(),
  2843.                         shippingNotes: $('#sales_form_shippingNotes').val(),
  2844.                         paymentNotes: $('#sales_form_paymentNotes').val(),
  2845.                         salesType: $('input[name="salesType"]').filter(':checked').val() || 'proforma',
  2846.                         salesStatus: $('#sales_form_status').val() ? $('#sales_form_status').val() : '0'
  2847.                     }
  2848.                 };
  2849.                 $.ajax({
  2850.                     url: "{{ path('app_admin_sales_create') }}",
  2851.                     method: 'POST',
  2852.                     data: salesData,
  2853.                     success: function(response) {
  2854.                         window.location.href = "/sales/detail/" + response.id;
  2855.                         Swal.fire({
  2856.                             icon: 'success',
  2857.                             title: 'Başarılı',
  2858.                             text: 'Satış başarıyla oluşturuldu'
  2859.                         });
  2860.                     },
  2861.                     error: function(xhr) {
  2862.                         var response = JSON.parse(xhr.responseText);
  2863.                         showErrorAlert(response.message);
  2864.                     }
  2865.                 });
  2866.             }
  2867.             function finalizeSalesSubmission() {
  2868.                 const saleTotal = convertToDecimal(getTotalSoldAmount());
  2869.                 const totalPayments = convertToDecimal(calculateTotalPaymentAmount());
  2870.                 const difference = saleTotal.minus(totalPayments).abs().toNumber();
  2871.                 if (difference > PAYMENT_TOLERANCE) {
  2872.                     showErrorAlert('Ödemeler satış toplamı ile hâlâ uyumsuz. Lütfen kontrol edin.');
  2873.                     return;
  2874.                 }
  2875.                 submitSalesRequest();
  2876.             }
  2877.             $('#confirm-auto-payment').on('click', function() {
  2878.                 if (!autoPaymentModalState) {
  2879.                     return;
  2880.                 }
  2881.                 const saleDate = autoPaymentModalState.saleDate || getSaleDateValue();
  2882.                 const autoPayment = createAutoPaymentPayload(autoPaymentModalState.amount, saleDate);
  2883.                 payments.push(autoPayment);
  2884.                 fetchPaymentForm();
  2885.                 calculateAndWriteDiffBetweenPaymentAndSolds();
  2886.                 $('#confirm-auto-payment').prop('disabled', true);
  2887.                 $('#autoPaymentModal').modal('hide');
  2888.                 autoPaymentModalState = null;
  2889.                 finalizeSalesSubmission();
  2890.             });
  2891.             $('#autoPaymentModal').on('hidden.bs.modal', function() {
  2892.                 $('#confirm-auto-payment').prop('disabled', false);
  2893.                 autoPaymentModalState = null;
  2894.             });
  2895.             ensureCashPaymentMethod();
  2896.             $(document).on('click', '.delete-row-button', function() {
  2897.                 const uuid = $(this).data('uuid');
  2898.                 deleteProductInSoldList(uuid);
  2899.                 $(`tr[id="${uuid}"]`).remove();
  2900.                 $(`.info-details-row-class[data-uuid="${uuid}"]`).remove();
  2901.                 fetchStocks();
  2902.                 updateTotalAmounts();
  2903.                 fetchPaymentForm();
  2904.                 $('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
  2905.             });
  2906.              // Handle unallocated quantity modal for rows
  2907.             $(document).on('click', '.open-unallocated-modal-row', function() {
  2908.                 const uuid = $(this).data('uuid');
  2909.                 const itemIndex = soldList.findIndex(item => item.uuid === uuid);
  2910.                 if (itemIndex === -1) return;
  2911.                 // Değeri gizli input yerine doğrudan soldList'ten oku
  2912.                 const currentQty = soldList[itemIndex].unAllocatedQuantity || 0;
  2913.                 $('#modal-unallocated-quantity').val(Number(currentQty).toFixed(2));
  2914.                 // Store current uuid for saving later
  2915.                 $('#unallocatedModal').data('current-uuid', uuid);
  2916.                 $('#unallocatedModal').modal('show');
  2917.             });
  2918.             // Save unallocated quantity from modal for rows
  2919.             $(document).on('click', '#save-unallocated-btn', function() {
  2920.                  const uuid = $('#unallocatedModal').data('current-uuid');
  2921.                     // Sadece satırlar için olan bölüm (uuid varsa)
  2922.                     if (uuid) {
  2923.                         const qty = $('#modal-unallocated-quantity').val() || 0;
  2924.                         const newUnallocatedQty = convertToDecimal(qty).toNumber();
  2925.                         // 1. soldList'teki ilgili ürünü bul ve unAllocatedQuantity'yi güncelle
  2926.                         const itemIndex = soldList.findIndex(item => item.uuid === uuid);
  2927.                         if (itemIndex > -1) {
  2928.                             soldList[itemIndex].unAllocatedQuantity = newUnallocatedQty;
  2929.                         }
  2930.                         // 2. DOĞRU SATIRI HEDEFLEYEREK butondaki sayıyı güncelle
  2931.                         const $button = $(`.open-unallocated-modal-row[data-uuid="${uuid}"]`);
  2932.                         $button.find('.unalloc-display').text(newUnallocatedQty.toFixed(2));
  2933.                         // 3. Tahsis edilmemiş miktar değiştirildi, artık otomatik hesaplama yapılmalı
  2934.                         delete rowHasManualTotal[uuid];
  2935.                         // 4. Satırın genel toplamlarını ve stokları güncelle
  2936.                         updateSoldProductFromRow(uuid);
  2937.                         $('#unallocatedModal').modal('hide');
  2938.                     } else { // Bu kısım adder-row için, dokunmuyoruz
  2939.                         const qty = $('#modal-unallocated-quantity').val() || 0;
  2940.                         $('#adder-unallocated-quantity').val(qty).trigger('change');
  2941.                         $('#unalloc-display').text(qty);
  2942.                         onInputChange.call($('#adder-unallocated-quantity')[0]);
  2943.                         $('#unallocatedModal').modal('hide');
  2944.                     }
  2945.                             });
  2946.             // Flag to prevent circular updates between unit price and total
  2947.             let isUpdatingFromTotal = false;
  2948.             let isUpdatingFromUnitPrice = false;
  2949.             // adder-row input alanlarının değişikliklerini dinleme
  2950.             $('#adder-row').on('input change',
  2951.                 '#adder-allocated-quantity, ' +
  2952.                 '#adder-unallocated-quantity, ' +
  2953.                 '#adder-price, ' +
  2954.                 '#adder-discount-select, ' +
  2955.                 '#adder-tva-select, ' +
  2956.                 '#adder-total', onInputChange);
  2957.             // Birim dönüşümünü yazma bittiğinde (blur) ve birim seçiminde (change) yap
  2958.             $('#adder-row').on('blur', '#adder-allocated-quantity', function(){
  2959.                 computeAndShowBaseUnitEquivalent();
  2960.             });
  2961.             $('#adder-row').on('change', '#adder-quantity-unit-select', function(){
  2962.                 computeAndShowBaseUnitEquivalent();
  2963.             });
  2964.             // Adder rowdaki input alanlarının değişikliklerini dinleme
  2965.             function onInputChange() {
  2966.                 if($(this).attr('id') === 'adder-total') {
  2967.                     // Toplam manuel değiştirildi → birim fiyatı hesapla, ama toplamı tekrar değiştirme
  2968.                     if(isUpdatingFromUnitPrice) return;
  2969.                     isUpdatingFromTotal = true;
  2970.                     const totalAmount = convertToDecimal($('#adder-total').val() || 0).toNumber();
  2971.                     const allocatedQuantity = convertToDecimal($('#adder-allocated-quantity').val() || 0).toNumber();
  2972.                     const unallocatedQuantity = convertToDecimal($('#adder-unallocated-quantity').val() || 0).toNumber();
  2973.                     const discountPercentage = convertToDecimal((($('#adder-discount-select').val() || '0') + '').replace('%', '')).toNumber();
  2974.                     const tvaPercentage = convertToDecimal((($('#adder-tva-select').val() || '0') + '').replace('%', '')).toNumber();
  2975.                     const unitPrice = calculateUnitPrice(totalAmount, allocatedQuantity, unallocatedQuantity, tvaPercentage, discountPercentage);
  2976.                     $('#adder-price').val(convertToDecimal(unitPrice).toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
  2977.                     setTimeout(function(){ isUpdatingFromTotal = false; }, 0);
  2978.                 } else {
  2979.                     // Birim fiyat, miktar, indirim veya KDV değişti → toplamı hesapla
  2980.                     if(isUpdatingFromTotal) return;
  2981.                     isUpdatingFromUnitPrice = true;
  2982.                     const allocatedQuantity = convertToDecimal($('#adder-allocated-quantity').val() || 0).toNumber();
  2983.                     const unallocatedQuantity = convertToDecimal($('#adder-unallocated-quantity').val() || 0).toNumber();
  2984.                     const price = convertToDecimal($('#adder-price').val() || 0).toNumber();
  2985.                     const discountPercentage = convertToDecimal((($('#adder-discount-select').val() || '0') + '').replace('%', '')).toNumber();
  2986.                     const tvaPercentage = convertToDecimal((($('#adder-tva-select').val() || '0') + '').replace('%', '')).toNumber();
  2987.                     calculateTotal(allocatedQuantity, unallocatedQuantity, price, tvaPercentage, discountPercentage);
  2988.                     setTimeout(function(){ isUpdatingFromUnitPrice = false; }, 0);
  2989.                 }
  2990.             }
  2991.             function roundToNearestCent(amount) {
  2992.                 return Math.round(amount * 100) / 100;
  2993.             }
  2994.             // Toplam tutarı hesaplama
  2995.             // Her ara adımda virgülden sonra 2 hane gösterilecek, 3. hane 5 ve üzeri ise yukarı yuvarlanacak
  2996.             function calculateTotal(allocatedQuantity, unallocatedQuantity, price, tvaPercentage, discountPercentage) {
  2997.                 const totals = calculateRowFinancials({
  2998.                     quantity: allocatedQuantity,
  2999.                     unAllocatedQuantity: unallocatedQuantity,
  3000.                     unitPrice: price,
  3001.                     discount: discountPercentage,
  3002.                     tax: tvaPercentage
  3003.                 });
  3004.                 $('#adder-total').val(totals.totalRounded.toFixed(2));
  3005.             }
  3006.             // Toplam tutardan Birim fiyatı hesaplama
  3007.             function calculateUnitPrice(totalAmount, allocatedQuantity, unallocatedQuantity, tvaPercentage, discountPercentage) {
  3008.                 const totalQuantity = convertToDecimal(allocatedQuantity || 0).plus(convertToDecimal(unallocatedQuantity || 0));
  3009.                 if (totalQuantity.isZero()) {
  3010.                     return 0;
  3011.                 }
  3012.                 const total = convertToDecimal(totalAmount || 0);
  3013.                 // Eğer toplam 0 ise, birim fiyat da 0 olmalı
  3014.                 if (total.isZero()) {
  3015.                     return 0;
  3016.                 }
  3017.                 const discountMultiplier = new Decimal(1).minus(convertToDecimal(discountPercentage || 0).div(100));
  3018.                 const taxMultiplier = new Decimal(1).plus(convertToDecimal(tvaPercentage || 0).div(100));
  3019.                 // %100 indirimde discountMultiplier = 0 olur, bu durumda birim fiyat hesaplanamaz
  3020.                 // Çünkü total = (unitPrice * quantity * (1 - discount) * (1 + tax))
  3021.                 // discount = 100% ise, unitPrice = 0 olmalı (ya da sonsuz, ama mantıklı olan 0)
  3022.                 if (discountMultiplier.isZero()) {
  3023.                     return 0;
  3024.                 }
  3025.                 if (taxMultiplier.isZero()) {
  3026.                     return 0;
  3027.                 }
  3028.                 // Formül: unitPrice = total / (quantity * (1 - discount%) * (1 + tax%))
  3029.                 const subtotal = total.div(taxMultiplier).div(discountMultiplier);
  3030.                 const unitPrice = subtotal.div(totalQuantity);
  3031.                 return unitPrice.toDecimalPlaces(6, MONEY_ROUNDING_MODE).toNumber();
  3032.             }
  3033.             function getProductCost(pid) {
  3034.                 // Popup için URL burada oluşturuluyor.
  3035.                 const url = '/product-cost/' + pid + '/' + getSelectedProduct().warehouseid;
  3036.                 $.ajax({
  3037.                     url: "/product-cost-detail/" + pid + '?warehouse=' + getSelectedProduct().warehouseid,
  3038.                     method: "POST",
  3039.                     dataType: "JSON",
  3040.                     success: function(data) {
  3041.                         var product = getSelectedProduct();
  3042.                         product.cost = data.cost;
  3043.                         product.measurementUnit = data.measurement;
  3044.                         $('.stock-info-span').html(data.stock.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " " + data.measurement);
  3045.                         $('.cost-info-span').html(data.cost.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " €"); // Eski detay butonu kaldırıldı
  3046.                         $('.measurement-unit-info-span').html(data.measurement);
  3047.                         $('#hidden-cost-input').val(data.cost);
  3048.                         // "Maliyet" popup açıyor maliyet detaylarını gösteriyor.
  3049.                         const detailsHtml = `
  3050.                             <div class="row align-items-center">
  3051.                                 <div class="col-md-3">${product.name}</div>
  3052.                                 <div class="col-md-3">
  3053.                                     <strong>
  3054.                                         <button type="button" class="btn btn-link btn-sm p-0 cost-popup-btn" data-url="${url}">Maliyet: ${data.cost.toFixed(2)} €</button>
  3055.                                     </strong>
  3056.                                 </div>
  3057.                                 <div class="col-md-3"><strong>Stok:</strong> ${data.stock.toFixed(2)} ${data.measurement}</div>
  3058.                                 <div class="col-md-3"><strong>Birim:</strong> ${data.measurement}</div>
  3059.                             </div>
  3060.                         `;
  3061.                         $('#info-details-content').html(detailsHtml);
  3062.                         return data;
  3063.                     },
  3064.                     error: function(data) {
  3065.                         $('#info-details-content').html('<div class="text-danger">Ürün maliyet bilgileri yüklenemedi.</div>');
  3066.                     }
  3067.                 });
  3068.             }
  3069.             $(document).on('click', '.cost-popup-btn', function() {
  3070.                 const url = $(this).data('url');
  3071.                 if (url) {
  3072.                     createPopup(url, 'cost-detail-window');
  3073.                 }
  3074.             });
  3075.             // Unallocated modal açıldığında mask'i uygula
  3076.             $('#unallocatedModal').on('shown.bs.modal', function() {
  3077.                 applyMasks();
  3078.             });
  3079.             // Unallocated modal aç/kapat ve değerleri senkronize et
  3080.             $(document).on('click', '#open-unallocated-modal', function() {
  3081.                 // Mevcut değerleri moda aktarma
  3082.                 const currentQty = $('#adder-unallocated-quantity').val() || 0;
  3083.                 const currentUnit = $('#adder-unallocated-quantity-unit-select').val() || '';
  3084.                 $('#modal-unallocated-quantity').val(Number(currentQty).toFixed(2));
  3085.                 // Seçili ürünün saklanan birimlerini modal select'ine doldur
  3086.                 const selectedProduct = getSelectedProduct();
  3087.                 if (selectedProduct && selectedProduct.convertibleUnits) {
  3088.                     populateSelectWithUnits($('#modal-unallocated-unit'), selectedProduct.convertibleUnits);
  3089.                 } else {
  3090.                     $('#modal-unallocated-unit').empty().append('<option value="">-</option>');
  3091.                 }
  3092.                 $('#modal-unallocated-unit').val(currentUnit);
  3093.                 $('#unallocatedModal').modal('show');
  3094.             });
  3095.             // 'Tahsis Edilmemiş Miktar' Modal'ındaki Kaydet Butonunun Düzeltilmiş Hali
  3096.             $(document).on('click', '#save-unallocated-btn', function() {
  3097.                 const uuid = $('#unallocatedModal').data('current-uuid');
  3098.                 const qty = $('#modal-unallocated-quantity').val() || 0;
  3099.                 const newUnallocatedQty = convertToDecimal(qty).toNumber();
  3100.                 // DURUM 1: Satış listesindeki bir satır güncelleniyor (uuid mevcut)
  3101.                 if (uuid) {
  3102.                     // 1. soldList'teki ilgili ürünü bul ve unAllocatedQuantity'yi güncelle
  3103.                     const itemIndex = soldList.findIndex(item => item.uuid === uuid);
  3104.                     if (itemIndex > -1) {
  3105.                         soldList[itemIndex].unAllocatedQuantity = newUnallocatedQty;
  3106.                     }
  3107.                     // 2. Sadece ilgili satırı ID ile hedefle ve içindeki göstergeyi güncelle
  3108.                     const $row = $(`#${uuid}`);
  3109.                     $row.find('.unalloc-display').text(newUnallocatedQty.toFixed(2));
  3110.                     // 3. Satırın genel toplamlarını ve stokları güncelle
  3111.                     updateSoldProductFromRow(uuid);
  3112.                 }
  3113.                 // DURUM 2: Adder-row (ekleme satırı) güncelleniyor (uuid mevcut değil)
  3114.                 else {
  3115.                     // Sadece adder-row'daki gizli input'u ve göstergeyi güncelle
  3116.                     $('#adder-unallocated-quantity').val(newUnallocatedQty).trigger('change');
  3117.                     $('#unalloc-display').text(newUnallocatedQty.toFixed(2));
  3118.                     // Adder-row'daki toplamı yeniden hesapla
  3119.                     onInputChange.call($('#adder-unallocated-quantity')[0]);
  3120.                 }
  3121.                 // İşlem tamamlandıktan sonra modalı kapat
  3122.                 $('#unallocatedModal').modal('hide');
  3123.                 // Modal'daki uuid verisini temizle ki bir sonraki açılışta karışmasın
  3124.                 $('#unallocatedModal').removeData('current-uuid');
  3125.             });
  3126.             function formatState(state) {
  3127.                 if (!state.id) {
  3128.                     return state.text;
  3129.                 }
  3130.                 var baseUrl = "/images";
  3131.                 var $state = $(
  3132.                     '<span><img src="' + baseUrl + '/' + state.element.dataset.image + '" class="img-flag list-image" style="width: 50px;"/> ' + state.text + '</span>'
  3133.                 );
  3134.                 return $state;
  3135.             };
  3136.             $('#productselect').select2({
  3137.                 theme: 'bootstrap4',
  3138.                 dropdownCssClass: 'custom-select-dropdown',
  3139.                 placeholder: 'Ürün Seçin',
  3140.                 allowClear: true
  3141.             });
  3142.             function showErrorAlert(message) {
  3143.                 Swal.fire({
  3144.                     icon: 'error',
  3145.                     title: 'Oops...',
  3146.                     text: '' + message,
  3147.                     background: 'white',
  3148.                 });
  3149.             }
  3150.             window.addEventListener("error", function(event) {
  3151.                 showErrorAlert(event.message);
  3152.             });
  3153.             function showSuccessAlert(message) {
  3154.                 Swal.fire({
  3155.                     icon: 'success',
  3156.                     title: 'Başarılı',
  3157.                     text: '' + message,
  3158.                     background: 'white',
  3159.                 });
  3160.             }
  3161.             $('#submitbtn').on('click', function(event) {
  3162.                 event.preventDefault();
  3163.                 if (soldList.length === 0) {
  3164.                     showErrorAlert("{% trans %} sale.errors.cannot_save_without_products {% endtrans %}");
  3165.                     return;
  3166.                 }
  3167.                 if (!validateSalesForm()) {
  3168.                     return;
  3169.                 }
  3170.                 const saleTotalValue = convertToDecimal(getTotalSoldAmount()).toNumber();
  3171.                 const currentPaymentTotal = convertToDecimal(calculateTotalPaymentAmount()).toNumber();
  3172.                 const saleDate = getSaleDateValue();
  3173.                 if (payments.length === 0 && saleTotalValue > PAYMENT_TOLERANCE) {
  3174.                     openAutoPaymentModal({
  3175.                         scenario: 'no-payment',
  3176.                         amount: saleTotalValue,
  3177.                         saleTotal: saleTotalValue,
  3178.                         currentPayments: currentPaymentTotal,
  3179.                         saleDate: saleDate
  3180.                     });
  3181.                     return;
  3182.                 }
  3183.                 const diffValue = convertToDecimal(saleTotalValue).minus(convertToDecimal(currentPaymentTotal)).toNumber();
  3184.                 if (Math.abs(diffValue) > PAYMENT_TOLERANCE) {
  3185.                     if (diffValue < 0) {
  3186.                         showErrorAlert('Girilen ödeme tutarı satış tutarını aşıyor. Lütfen ödeme kayıtlarını kontrol edin.');
  3187.                         return;
  3188.                     }
  3189.                     openAutoPaymentModal({
  3190.                         scenario: 'mismatch',
  3191.                         amount: diffValue,
  3192.                         saleTotal: saleTotalValue,
  3193.                         currentPayments: currentPaymentTotal,
  3194.                         saleDate: saleDate
  3195.                     });
  3196.                     return;
  3197.                 }
  3198.                 submitSalesRequest();
  3199.             });
  3200.             function addCommas(number) {
  3201.                 let decimals = 2;
  3202.                 number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
  3203.                 var n = !isFinite(+number) ? 0 : +number;
  3204.                 var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
  3205.                 var sep = ',';
  3206.                 var dec = '.';
  3207.                 var s = '';
  3208.                 var toFixedFix = function(n, prec) {
  3209.                     var k = Math.pow(10, prec);
  3210.                     return '' + (Math.round(n * k) / k).toFixed(prec);
  3211.                 };
  3212.                 s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
  3213.                 if (s[0].length > 3) {
  3214.                     s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
  3215.                 }
  3216.                 if ((s[1] || '').length < prec) {
  3217.                     s[1] = s[1] || '';
  3218.                     s[1] += new Array(prec - s[1].length + 1).join('0');
  3219.                 }
  3220.                 return s.join(dec);
  3221.             }
  3222.             $(document).on('input change', '#products_sold_totalUnitPurchasePrice, #products_sold_quantity, #products_sold_unAllocatedQuantity, #products_sold_discount, #products_sold_tax', function() {
  3223.                 var purchasePrice = $('#products_sold_totalUnitPurchasePrice').val() || "0";
  3224.                 var purchasePriceClean = parseFloat(purchasePrice.replace(/,/g, "")) || 0;
  3225.                 var cost = parseFloat($('#hidden-cost-input').val()) || 0;
  3226.                 var quantity = $('#products_sold_quantity').val() || "0";
  3227.                 var quantityClean = parseFloat(quantity.replace(/,/g, "")) || 0;
  3228.                 var unAllocatedQuantity = $('#products_sold_unAllocatedQuantity').val() || "0";
  3229.                 var unAllocatedQuantityClean = parseFloat(unAllocatedQuantity.replace(/,/g, "")) || 0;
  3230.                 var discountPercentage = $('#products_sold_discount').val() || "0";
  3231.                 var discountPercentageClean = parseFloat(discountPercentage.replace(/,/g, "")) || 0;
  3232.                 var taxPercentage = $('#products_sold_tax').val() || "0";
  3233.                 var taxPercentageClean = parseFloat(taxPercentage.replace(/,/g, "")) || 0;
  3234.                 var totalQuantity = quantityClean + unAllocatedQuantityClean;
  3235.                 var totalSalePriceBeforeDiscount = purchasePriceClean * totalQuantity;
  3236.                 var discountAmount = (totalSalePriceBeforeDiscount * discountPercentageClean) / 100;
  3237.                 var totalSalePriceAfterDiscount = totalSalePriceBeforeDiscount - discountAmount;
  3238.                 var taxAmount = (totalSalePriceAfterDiscount * taxPercentageClean) / 100;
  3239.                 var totalSalePrice = totalSalePriceAfterDiscount + taxAmount;
  3240.                 $('#products_sold_totalSalePrice').val(addCommas(totalSalePrice.toFixed(2)));
  3241.                 $('#products_sold_totalPuchasePrice').val(addCommas(totalSalePrice.toFixed(2)));
  3242.                 cost = roundToDecimal(cost);
  3243.                 var salePriceWithoutTax = totalSalePrice / (1 + taxPercentageClean / 100);
  3244.                 var totalCost = totalQuantity * cost;
  3245.                 var profit = salePriceWithoutTax - totalCost;
  3246.                 var profitSpan;
  3247.                 if (profit > 0) {
  3248.                     profitSpan = "<span style='color:green'>" + profit.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " €</span>";
  3249.                 } else {
  3250.                     profitSpan = "<span style='color:red'>" + profit.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " €</span>";
  3251.                 }
  3252.                 $('.profit-info-span').html(profitSpan);
  3253.             });
  3254.             $('#payment_amount').mask("#,##0.00", {
  3255.                 reverse: true
  3256.             });
  3257.             // Show/Hide Voucher Code field based on Payment Method
  3258.             $('#payment_paymentMethod').on('change', function() {
  3259.                 var selectedText = $(this).find("option:selected").text().trim().toLowerCase();
  3260.                 var $voucherField = $('#payment_voucherCode');
  3261.                 var $voucherLabel = $('label[for="payment_voucherCode"]');
  3262.                 
  3263.                 if (selectedText.includes('hediye') || selectedText.includes('gift') || selectedText.includes('çek')) {
  3264.                     $voucherField.show();
  3265.                     $voucherLabel.show();
  3266.                     $voucherField.attr('required', 'required');
  3267.                 } else {
  3268.                     $voucherField.hide();
  3269.                     $voucherLabel.hide();
  3270.                     $voucherField.val('');
  3271.                     $voucherField.removeAttr('required');
  3272.                 }
  3273.             });
  3274.             $('#sales_form_customer').select2({
  3275.                 theme: 'bootstrap4',
  3276.                 dropdownCssClass: 'custom-select-dropdown',
  3277.                 placeholder: 'Müşteri Seçin',
  3278.                 allowClear: true
  3279.             });
  3280.             $('.js-edit-customer').on('click', function() {
  3281.                 let customerId = $('#sales_form_customer').val();
  3282.                 if (!customerId) {
  3283.                     showErrorAlert("{% trans %} selectCustomerForEdit {% endtrans %}");
  3284.                 } else {
  3285.                     $.ajax({
  3286.                         url: '/admin/customer/detail/' + customerId,
  3287.                         type: 'GET',
  3288.                         dataType: 'json',
  3289.                         success: function(response) {
  3290.                             showUpdateModal(customerId, response);
  3291.                         },
  3292.                         error: function(jqXHR, textStatus, errorThrown) {
  3293.                             if (jqXHR.status === 404) {
  3294.                                 showErrorAlert("Seçilen müşteri veritabanında bulunamadı.");
  3295.                             } else {
  3296.                                 showErrorAlert('Müşteri bilgileri yüklenirken bir hata oluştu.');
  3297.                             }
  3298.                             clearCustomerFields();
  3299.                         }
  3300.                     });
  3301.                 }
  3302.             });
  3303.             $('#sales_form_customer').on('change', function() {
  3304.                 let customerId = $(this).val();
  3305.                 if (!customerId) {
  3306.                     clearCustomerFields();
  3307.                     return;
  3308.                 }
  3309.                 $.ajax({
  3310.                     url: '/admin/customer/detail/' + customerId,
  3311.                     type: 'GET',
  3312.                     dataType: 'json',
  3313.                     success: function(response) {
  3314.                         if (!response.phone || !response.email || !response.address) {
  3315.                             showUpdateModal(customerId, response);
  3316.                         } else {
  3317.                             populateCustomerFields(response);
  3318.                         }
  3319.                     },
  3320.                     error: function(jqXHR, textStatus, errorThrown) {
  3321.                         if (jqXHR.status === 404) {
  3322.                             showErrorAlert("Seçilen müşteri veritabanında bulunamadı.");
  3323.                         } else {
  3324.                             showErrorAlert('Müşteri bilgileri yüklenirken bir hata oluştu.');
  3325.                         }
  3326.                         clearCustomerFields();
  3327.                     }
  3328.                 });
  3329.             });
  3330.             function populateCustomerFields(customer) {
  3331.                 $('#update_customer_fullName').val(customer.fullName);
  3332.                 $('#update_customer_email').val(customer.email);
  3333.                 $('#update_customer_phone').val(customer.phone);
  3334.                 $('#update_customer_address').val(customer.address);
  3335.             }
  3336.             function clearCustomerFields() {
  3337.                 $('#update_customer_fullName, #update_customer_email, #update_customer_phone, #update_customer_address').val('');
  3338.             }
  3339.             function showUpdateModal(id, customer) {
  3340.                 $('#update_customer_id').val(id);
  3341.                 $('#update_customer_email').val(customer.email || '');
  3342.                 $('#update_customer_phone').val(customer.phone || '');
  3343.                 $('#update_customer_address').val(customer.address || '');
  3344.                 $('#update_customer_fullName').val(customer.fullName || '');
  3345.                 $('#updateCustomerModal').modal('show');
  3346.             }
  3347.             $('#updateCustomerForm').on('submit', function(e) {
  3348.                 e.preventDefault();
  3349.                 let customerId = $('#update_customer_id').val();
  3350.                 let formData = {
  3351.                     email: $('#update_customer_email').val(),
  3352.                     phone: $('#update_customer_phone').val(),
  3353.                     address: $('#update_customer_address').val(),
  3354.                     fullName: $('#update_customer_fullName').val()
  3355.                 };
  3356.                 $.ajax({
  3357.                     url: '/admin/customer/update/' + customerId,
  3358.                     type: 'POST',
  3359.                     data: formData,
  3360.                     success: function(response) {
  3361.                         $('#updateCustomerModal').modal('hide');
  3362.                         populateCustomerFields(response);
  3363.                         showSuccessAlert("{% trans %} customerUpdateSuccess {% endtrans %}");
  3364.                     },
  3365.                     error: function(jqXHR, textStatus, errorThrown) {
  3366.                         let errorMessage = 'Güncelleme sırasında bilinmeyen bir hata oluştu.';
  3367.                         try {
  3368.                             const response = JSON.parse(jqXHR.responseText);
  3369.                             if (response && response.message) {
  3370.                                 errorMessage = response.message;
  3371.                             }
  3372.                         } catch (e) {
  3373.                             console.error("JSON Parse Error:", e);
  3374.                             console.error("Server Response:", jqXHR.responseText);
  3375.                         }
  3376.                         showErrorAlert(errorMessage);
  3377.                     }
  3378.                 });
  3379.             });
  3380.             $('#show-info-btn').on('click', function(e) {
  3381.                 e.preventDefault();
  3382.                 const selectedProductId = $('#productselect').val();
  3383.                 if (!selectedProductId || selectedProductId <= 0) {
  3384.                     showErrorAlert("Detayları görmek için önce bir ürün seçmelisiniz.");
  3385.                     return;
  3386.                 }
  3387.                 const $infoContent = $('#info-details-content');
  3388.                 const $icon = $(this).find('i');
  3389.                 $infoContent.toggle();
  3390.                 if ($infoContent.is(':visible')) {
  3391.                     $icon.removeClass('fa-info-circle').addClass('fa-chevron-up');
  3392.                 } else {
  3393.                     $icon.removeClass('fa-chevron-up').addClass('fa-info-circle');
  3394.                 }
  3395.             });
  3396.             // --- GIFT VOUCHER LOGIC ---
  3397.             // Hediye Kuponu Butonuna Tıklama (Desktop)
  3398.             $('#btn-gift-voucher').on('click', function() {
  3399.                 openGiftVoucherModal();
  3400.             });
  3401.              // Hediye Kuponu Butonuna Tıklama (Mobile)
  3402.             $('#btn-gift-voucher-mobile').on('click', function() {
  3403.                 openGiftVoucherModal();
  3404.             });
  3405.             function openGiftVoucherModal() {
  3406.                 const customerId = $('#sales_form_customer').val();
  3407.                 if (!customerId) {
  3408.                     showErrorAlert('Lütfen önce bir müşteri seçiniz.');
  3409.                     return;
  3410.                 }
  3411.                 // API'den bakiye sorgula
  3412.                 $.ajax({
  3413.                     url: '/admin/gift-certificates/api/balance/' + customerId,
  3414.                     method: 'GET',
  3415.                     success: function(response) {
  3416.                         if (response.error) {
  3417.                             showErrorAlert(response.error);
  3418.                             return;
  3419.                         }
  3420.                         $('#voucher-total-balance').text(response.formatted);
  3421.                         $('#voucher-total-balance').data('balance', response.balance);
  3422.                         $('#voucher-customer-name').text(response.customerName); // Set customer name
  3423.                         $('#voucher-use-amount').val(''); // Inputu temizle
  3424.                          // Varsayılan olarak kalanı doldurmayı deneyebiliriz ama kullanıcı girişi daha iyi
  3425.                         const totals = aggregateSoldTotals();
  3426.                          const paymentTotal = convertToDecimal(calculateTotalPaymentAmount());
  3427.                         const remaining = totals.grandTotal.minus(paymentTotal);
  3428.                          if (remaining.greaterThan(0)) {
  3429.                              // Eğer kalan miktar, bakiyeden küçükse kalanı, büyükse bakiyeyi öner
  3430.                              const balance = new Decimal(response.balance);
  3431.                              const suggested = remaining.lessThan(balance) ? remaining : balance;
  3432.                              $('#voucher-use-amount').val(suggested.toNumber());
  3433.                          }
  3434.                         $('#gift-voucher-usage-modal').modal('show');
  3435.                     },
  3436.                     error: function() {
  3437.                         showErrorAlert('Müşteri hediye çeki bakiyesi sorgulanırken bir hata oluştu.');
  3438.                     }
  3439.                 });
  3440.             }
  3441.             // Hediye Çeki Kullanımını Onayla
  3442.             $('#btn-confirm-voucher-usage').on('click', function() {
  3443.                 const amount = parseFloat($('#voucher-use-amount').val());
  3444.                 const balance = parseFloat($('#voucher-total-balance').data('balance'));
  3445.                 if (isNaN(amount) || amount <= 0) {
  3446.                     showErrorAlert('Lütfen geçerli bir miktar giriniz.');
  3447.                     return;
  3448.                 }
  3449.                 if (amount > balance) {
  3450.                     showErrorAlert('Girdiğiniz miktar, toplam bakiyeden fazla olamaz.');
  3451.                     return;
  3452.                 }
  3453.                 // Ödeme Yöntemini Bul (Hediye Çeki / Gift Voucher)
  3454.                 let voucherMethodId = null;
  3455.                 // 1. Yöntem: Backend'den gelen veriyi kullan (En Sağlam)
  3456.                 // 1. Yöntem: Backend'den gelen veriyi kullan (En Sağlam)
  3457.                 {% if paymentMethods is defined %}
  3458.                     const methods = [
  3459.                         {% for pm in paymentMethods %}
  3460.                             { id: "{{ pm.id }}", name: "{{ pm.name|e('js') }}" },
  3461.                         {% endfor %}
  3462.                     ];
  3463.                     const voucherMethod = methods.find(m => 
  3464.                         (m.name && m.name.toLowerCase().includes('hediye')) || 
  3465.                         (m.name && m.name.toLowerCase().includes('gift')) || 
  3466.                         (m.name && m.name.toLowerCase().includes('kupon'))
  3467.                     );
  3468.                     if (voucherMethod) {
  3469.                         voucherMethodId = voucherMethod.id;
  3470.                     }
  3471.                 {% endif %}
  3472.                 // 2. Yöntem: Eğer yukarıdaki çalışmazsa DOM'dan bulmayı dene
  3473.                 if (!voucherMethodId) {
  3474.                      const $methodSelect = $('select[name="paymentMethod[]"]').first(); // Tablodaki herhangi bir select
  3475.                      $methodSelect.find('option').each(function() {
  3476.                         const text = $(this).text().toLowerCase();
  3477.                         if (text.includes('hediye') || text.includes('gift') || text.includes('kupon')) {
  3478.                             voucherMethodId = $(this).val();
  3479.                             return false; // break
  3480.                         }
  3481.                     });
  3482.                 }
  3483.                 
  3484.                 // 3. Yöntem: Modal Select
  3485.                 if (!voucherMethodId) {
  3486.                      const $modalSelect = $('#add-payment-modal select[name="paymentMethod"]');
  3487.                      $modalSelect.find('option').each(function() {
  3488.                         const text = $(this).text().toLowerCase();
  3489.                         if (text.includes('hediye') || text.includes('gift') || text.includes('kupon')) {
  3490.                             voucherMethodId = $(this).val();
  3491.                             return false; // break
  3492.                         }
  3493.                     });
  3494.                 }
  3495.                 if (!voucherMethodId) {
  3496.                      // Eğer modal select'inde yoksa, bir de manuel tanımlı ID varsa onu kontrol et (opsiyonel)
  3497.                      // Veya uyarı ver
  3498.                      // Fallback: İlk metodun id'sini alma riskine girmeyelim, kullanıcıya soralım veya hata verelim.
  3499.                      // Ancak genelde bir "Hediye Çeki" metodu olmalı.
  3500.                      // Şimdilik hata verelim.
  3501.                      showErrorAlert('Sistemde "Hediye Çeki" ödeme yöntemi bulunamadı. Lütfen yönetim panelinden ekleyiniz.');
  3502.                      return;
  3503.                 }
  3504.                 // Yeni ödeme satırı ekle
  3505.                 const payment = {
  3506.                     uuid: crypto.randomUUID(),
  3507.                     amount: amount,
  3508.                     status: "0", // Ödendi
  3509.                     paymentDate: new Date().toISOString().split('T')[0], // Bugün
  3510.                     paymentDueDate: new Date().toISOString().split('T')[0], // Bugün
  3511.                     paymentMethod: voucherMethodId,
  3512.                     description: 'Hediye Çeki Kullanımı',
  3513.                     voucherCode: 'AUTO_DEDUCT_FROM_BALANCE' // Backend bunu işleyip ilgili çeklerden düşebilir
  3514.                 };
  3515.                 payments.push(payment);
  3516.                 fetchPaymentForm();
  3517.                 fetchSoldListRows(); // Trigger AVOIR row update
  3518.                 $('#gift-voucher-usage-modal').modal('hide');
  3519.                 showSuccessAlert(amount + ' TL hediye çeki ödemesi eklendi.');
  3520.             });
  3521.             // calculateTotalPaymentAmount fonksiyonunu global scope'ta bulamadıysak buraya ekleyelim veya mevcut olanı kullanalım
  3522.             function calculateTotalPaymentAmount() {
  3523.                 let total = new Decimal(0);
  3524.                 payments.forEach(function(p) {
  3525.                    total = total.plus(convertToDecimal(p.amount));
  3526.                 });
  3527.                 return total;
  3528.             }
  3529.             // --- END GIFT VOUCHER LOGIC ---
  3530.         });
  3531.         function createPopup(url, window_name = 'example-popup') {
  3532.             var width = 1100;
  3533.             var height = 700;
  3534.             var screen_width = window.screen.width;
  3535.             var screen_height = window.screen.height;
  3536.             var popup_left = (screen_width - width) / 2;
  3537.             var popup_top = (screen_height - height) / 2;
  3538.             var popup_window = window.open(url, window_name, 'width=' + width + ',height=' + height + ',left=' + popup_left + ',top=' + popup_top);
  3539.         }
  3540.     </script>
  3541. {% endblock %}