<?php
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
use Twig\Extension\SandboxExtension;
use Twig\Markup;
use Twig\Sandbox\SecurityError;
use Twig\Sandbox\SecurityNotAllowedTagError;
use Twig\Sandbox\SecurityNotAllowedFilterError;
use Twig\Sandbox\SecurityNotAllowedFunctionError;
use Twig\Source;
use Twig\Template;
use Twig\TemplateWrapper;
/* admin/sales/index.html.twig */
class __TwigTemplate_3ff25a21a84d6e047978bc8154c2f3e1 extends Template
{
private Source $source;
/**
* @var array<string, Template>
*/
private array $macros = [];
public function __construct(Environment $env)
{
parent::__construct($env);
$this->source = $this->getSourceContext();
$this->blocks = [
'title' => [$this, 'block_title'],
'head' => [$this, 'block_head'],
'body' => [$this, 'block_body'],
'javascript' => [$this, 'block_javascript'],
];
}
protected function doGetParent(array $context): bool|string|Template|TemplateWrapper
{
// line 1
return "base.html.twig";
}
protected function doDisplay(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "admin/sales/index.html.twig"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "admin/sales/index.html.twig"));
$this->parent = $this->load("base.html.twig", 1);
yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks));
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
}
// line 3
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_title(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "title"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "title"));
// line 4
yield " ";
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("sales", [], "messages");
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 9
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_head(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "head"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "head"));
// line 10
yield " <style>
/* Keep all form elements at 12px on this page */
select, textarea, button { font-size: 12px !important; }
.form-control, .custom-select { font-size: 12px !important; }
/* Select2 text sizes */
.select2-container .select2-selection__rendered,
.select2-results__option,
.select2-container .select2-search__field { font-size: 12px !important; }
#adder-allocated-quantity,
#adder-quantity-unit {
vertical-align: middle;
}
.product-select-cell {
max-width: 250px;
/* Hücrenin alabileceği maksimum genişlik. İstediğiniz gibi ayarlayın. */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Ekleme Satırı Stilleri */
#adder-row {
background-color: #f8f9fa;
/* Hafif gri arkaplan */
}
#adder-row td {
vertical-align: middle;
padding: 0.25rem; /* Boşluk azaltıldı */
border-top: 2px dashed #e3e6f0;
}
/* Adder satırında hücreler arası yatay boşluğu minimuma indir */
#adder-row .input-group { margin-right: 4px; }
#adder-row .btn-icon, #adder-row .btn { margin-right: 4px; }
/* Yeni Card Header Stili */
.bg-light-blue {
background-color: #f0f3ff !important;
/* Hafif mavi arkaplan */
}
#product-list-table-tbody input {
/*border: none;*/
/*padding: 0;*/
/*margin: 0;*/
background-color: white;
}
.info-span {
color: red;
font-weight: bold;
}
.cost-detail-button {
color: blue;
font-width: bold;
border: none;
background-color: transparent;
}
.attention-icon {
display: inline-block;
vertical-align: middle;
margin-left: 5px;
font-size: 16px;
color: red;
font-weight: bold;
}
.quantity-input-added {
width: 60px;
}
.select2-container {
width: 100% !important;
/* Genişlik uyumluluğu için */
}
/* En kritik CSS düzeltmesi olmasa da, z-index çakışmalarına karşı bir güvencedir.
Asıl çözüm JavaScript'tedir. */
.select2-container--open {
z-index: 1056 !important;
}
.select2-container {
width: 100% !important;
}
.select2-container--open {
z-index: 1056 !important;
}
.select2-container--bootstrap4 .select2-selection--single
{
padding: .50rem !important;
}
.select2-container .select2-selection--single {
height: 28px !important;
}
/* === SEÇİM KARTLARI İÇİN CSS === */
.selection-card-label {
display: block;
cursor: pointer;
width: 100%;
}
.selection-card {
border: 2px solid #e3e6f0;
border-radius: 0.5rem;
padding: 0.50rem;
transition: all 0.2s ease-in-out;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
/* Kartın üzerine gelindiğinde (hover) */
.selection-card:hover {
border-color: #4e73df;
transform: translateY(-2px);
/* Hafif yukarı kalkma efekti */
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
/* Kart içindeki başlık (Fatura, Proforma) */
.selection-title {
font-size: 1.1rem;
font-weight: 500;
color: #5a5c69;
transition: color 0.2s ease-in-out;
}
/* Onay işareti (başlangıçta gizli) */
.checkmark {
font-size: 1.25rem;
color: #4e73df;
opacity: 0;
transform: scale(0.5);
transition: all 0.2s ease-out;
margin-left: 0.75rem;
}
/* SEÇİM YAPILDIĞINDAKİ STİLLER */
/* CSS :has() seçicisi ile, içindeki radio input seçili olan etiketin kartını stillendiriyoruz. */
.selection-card-label:has(input[type=\"radio\"]:checked) .selection-card {
border-color: #4e73df;
background-color: #f0f3ff;
}
/* Seçili kartın başlık rengini değiştir */
.selection-card-label:has(input[type=\"radio\"]:checked) .selection-title {
color: #4e73df;
font-weight: 600;
}
/* Seçili kartın onay işaretini görünür yap */
.selection-card-label:has(input[type=\"radio\"]:checked) .checkmark {
opacity: 1;
transform: scale(1);
}
/* Bilgi satırı için stil */
#info-details-content {
background-color: #f1faff;
/* Hafif mavi arka plan */
border-bottom: 2px dashed #e3e6f0;
/* Altına kesikli çizgi ekler */
}
.info-details-content-class {
background-color: #f1faff;
/* Hafif mavi arka plan */
border-bottom: 2px dashed #e3e6f0;
/* Altına kesikli çizgi ekler */
}
/* Make product table full-width and remove right gaps */
.card-body > .table-responsive { padding-right: 0; }
#product-list-table { width: 100% !important; table-layout: fixed; }
#product-list-table th, #product-list-table td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#product-list-table .product-select-cell select { width: 100%; }
/* Adjust specific column widths to fit viewport */
.summary-row td {
border-top: none;
background-color: #f8f9fc;
padding: 0;
}
.sale-summary-row {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
padding: 0.35rem 0.75rem;
border-radius: 0.35rem;
margin-right: 1.5rem;
}
.sale-summary-row .summary-label {
font-weight: 600;
color: #4b5563;
white-space: nowrap;
}
.sale-summary-row .summary-value {
font-weight: 700;
color: #111827;
text-align: right;
min-width: 120px;
white-space: nowrap;
}
.sale-summary-row.summary-tax .summary-label,
.sale-summary-row.summary-tax .summary-value {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.sale-summary-row.summary-tax .summary-label div,
.sale-summary-row.summary-tax .summary-value div {
white-space: nowrap;
}
.sale-summary-row.summary-total .summary-value {
font-size: 1.25rem;
}
#product-list-table th:nth-child(1), #product-list-table td:nth-child(1) { width: 5%; }
#product-list-table th:nth-child(2), #product-list-table td:nth-child(2) { width: 25%; }
#product-list-table th:nth-child(3), #product-list-table td:nth-child(3) { width: 10%; }
#product-list-table th:nth-child(4), #product-list-table td:nth-child(4) { width: 10%; }
#product-list-table th:nth-child(5), #product-list-table td:nth-child(5) { width: 10%; }
#product-list-table th:nth-child(6), #product-list-table td:nth-child(6) { width: 8%; }
#product-list-table th:nth-child(7), #product-list-table td:nth-child(7) { width: 8%; }
#product-list-table th:nth-child(8), #product-list-table td:nth-child(8) { width: 15%; }
#product-list-table th:nth-child(9), #product-list-table td:nth-child(9) { width: 9%; }
</style>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 256
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_body(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "body"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "body"));
// line 257
yield " <div class=\"container-fluid m-0 p-0\">
<div class=\"card shadow mb-4\">
<div class=\"card-header py-3 d-flex justify-content-between align-items-center\">
<div class=\"btn-group\" role=\"group\">
<div class=\"d-md-none\">
<div class=\"dropdown\">
<button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">
";
// line 265
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("actions", [], "messages");
// line 266
yield " </button>
<div class=\"dropdown-menu\" aria-labelledby=\"dropdownMenuButton\">
<button type=\"button\" class=\"dropdown-item\" data-toggle=\"modal\" data-target=\"#payments-modal\">
<i class=\"fas fa-money-check-alt mr-1\"></i>
";
// line 270
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("payments", [], "messages");
// line 271
yield " </button>
<button type=\"button\" data-toggle=\"modal\" data-target=\"#add-customer-modal\" class=\"dropdown-item\">
<i class=\"fas fa-user-plus mr-1\"></i>
";
// line 274
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("addCustomer", [], "messages");
// line 275
yield " </button>
<button type=\"button\" class=\"dropdown-item js-edit-customer\">
<i class=\"fas fa-user-edit mr-1\"></i>
";
// line 278
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("editCustomer", [], "messages");
// line 279
yield " </button>
<button type=\"button\" class=\"dropdown-item\" id=\"btn-gift-voucher-mobile\">
<i class=\"fas fa-gift mr-1\"></i>
";
// line 282
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("giftVoucher"), "html", null, true);
yield "
</button>
</div>
</div>
</div>
<div class=\"d-none d-md-block\">
<div class=\"btn-group\" role=\"group\">
<button type=\"button\" class=\"btn btn-outline-secondary\" data-toggle=\"modal\" data-target=\"#payments-modal\">
<i class=\"fas fa-money-check-alt mr-1\"></i>
";
// line 292
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("payments", [], "messages");
// line 293
yield " </button>
<button type=\"button\" data-toggle=\"modal\" data-target=\"#add-customer-modal\" class=\"btn btn-outline-secondary\">
<i class=\"fas fa-user-plus mr-1\"></i>
";
// line 296
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("addCustomer", [], "messages");
// line 297
yield " </button>
<button type=\"button\" class=\"btn btn-outline-secondary js-edit-customer\">
";
// line 299
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("editCustomer", [], "messages");
// line 300
yield " </button>
<button type=\"button\" class=\"btn btn-outline-secondary\" id=\"btn-gift-voucher\" title=\"";
// line 301
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("useGiftVoucher"), "html", null, true);
yield "\">
<i class=\"fas fa-gift mr-1\"></i>
";
// line 303
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("giftVoucher"), "html", null, true);
yield "
</button>
</div>
</div>
</div>
<button type=\"button\" id=\"submitbtn\" class=\"btn btn-success\">
<i class=\"fas fa-save mr-1\"></i>
";
// line 312
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("save", [], "messages");
// line 315
yield " </button>
</div>
<div class=\"card-body\">
";
// line 319
yield " <div class=\"form-group\">
<label class=\"form-label font-weight-bold\">";
// line 320
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("salesType"), "html", null, true);
yield "</label>
<div class=\"row\">
";
// line 323
yield " <div class=\"col-md-6\">
<label class=\"selection-card-label\">
<input type=\"radio\" name=\"salesType\" class=\"d-none\" value=\"invoice\">
<div class=\"selection-card\">
<span class=\"selection-title\">";
// line 327
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("invoice", [], "messages");
yield "</span>
<i class=\"fas fa-check-circle checkmark\"></i>
</div>
</label>
</div>
";
// line 334
yield " <div class=\"col-md-6\">
<label class=\"selection-card-label\">
<input type=\"radio\" name=\"salesType\" class=\"d-none\" value=\"proforma\" checked>
<div class=\"selection-card\">
<span class=\"selection-title\">";
// line 338
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("proforma", [], "messages");
yield "</span>
<i class=\"fas fa-check-circle checkmark\"></i>
</div>
</label>
</div>
</div>
</div>
<div style=\"margin-bottom: 10px\">
";
// line 347
yield " ";
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 347, $this->source); })()), 'form_start');
yield "
<div class=\"\">
<div class=\"row\">
<div class=\"col-lg-6 col-md-12\">
";
// line 351
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 351, $this->source); })()), "totalPurchasePrice", [], "any", false, false, false, 351), 'row');
yield "
";
// line 352
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 352, $this->source); })()), "status", [], "any", false, false, false, 352), 'row');
yield "
</div>
<div class=\"col-lg-6 col-md-12\">
";
// line 355
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 355, $this->source); })()), "customer", [], "any", false, false, false, 355), 'row');
yield "
";
// line 356
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 356, $this->source); })()), "seller", [], "any", false, false, false, 356), 'row');
yield "
</div>
</div>
<div class=\"row\">
<div class=\"col-lg-3 col-md-6\">";
// line 360
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 360, $this->source); })()), "salesDate", [], "any", false, false, false, 360), 'row');
yield "</div>
<div class=\"col-lg-3 col-md-6\">";
// line 361
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 361, $this->source); })()), "deliveryDate", [], "any", false, false, false, 361), 'row');
yield "</div>
<div class=\"col-lg-3 col-md-6\">";
// line 362
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 362, $this->source); })()), "dueDate", [], "any", false, false, false, 362), 'row');
yield "</div>
<div class=\"col-lg-3 col-md-6\">";
// line 363
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock(CoreExtension::getAttribute($this->env, $this->source, (isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 363, $this->source); })()), "validDate", [], "any", false, false, false, 363), 'row');
yield "</div>
</div>
</div>
";
// line 367
yield "
";
// line 369
yield " <div class=\"modal fade\" id=\"payments-modal\" role=\"dialog\" aria-labelledby=\"paymentsModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog modal-xl\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"paymentsModalLabel\">";
// line 373
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("payments", [], "messages");
yield "</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
<div class=\"card mb-1\">
<div class=\"card-header d-flex align-items-center justify-content-between\">
<div>
<h5 class=\"mb-0\">";
// line 382
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("payments", [], "messages");
yield "</h5>
<div style=\"float: left; width: 250px\">
";
// line 384
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("total", [], "messages");
yield ":
<span id=\"total-amount-span\"></span>
";
// line 386
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("remainingAmount", [], "messages");
yield ":
<span id=\"remainder-amount-span\"></span>
</div>
</div>
<button class=\"btn btn-primary\" type=\"button\" id=\"btn-addPayment\" data-toggle=\"modal\" data-target=\"#add-payment-modal\">
";
// line 391
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("addPayment", [], "messages");
// line 392
yield " </button>
</div>
<div class=\"card-body\">
<div>
<table class=\"table\" id=\"payment-list-table\">
<thead>
<tr>
<th>";
// line 399
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("amount", [], "messages");
yield "</th>
<th>";
// line 400
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("status", [], "messages");
yield "</th>
<th>";
// line 401
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("paymentDate", [], "messages");
yield "</th>
<th>";
// line 402
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("dueDate", [], "messages");
yield "</th>
<th>";
// line 403
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("paymentMethod", [], "messages");
yield "</th>
<th>";
// line 404
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("description", [], "messages");
yield "</th>
<th> </th>
</tr>
</thead>
<tbody id=\"payment-list-table-tbody\"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class=\"card border-0 shadow-sm\">
<div class=\"card-header bg-light-blue border-0 py-3\">
<div class=\"row align-items-center\">
<div class=\"col-md-4\">
<label for=\"warehouse-select\" class=\"font-weight-bold mb-1\">";
// line 422
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("warehouse", [], "messages");
yield "</label>
<select id=\"warehouseselect\" class=\"form-control\">
<option value=\"\">";
// line 424
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("selectwarehouse", [], "messages");
yield "</option>
";
// line 425
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable((isset($context["warehouses"]) || array_key_exists("warehouses", $context) ? $context["warehouses"] : (function () { throw new RuntimeError('Variable "warehouses" does not exist.', 425, $this->source); })()));
foreach ($context['_seq'] as $context["_key"] => $context["warehouse"]) {
// line 426
yield " <option value=\"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["warehouse"], "id", [], "any", false, false, false, 426), "html", null, true);
yield "\">";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["warehouse"], "name", [], "any", false, false, false, 426), "html", null, true);
yield "</option>
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['warehouse'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 428
yield " </select>
</div>
<div class=\"col-md-5\"></div>
<div id=\"product-info-box\" class=\"col-md-3\" style=\"display: none;\">
<div class=\"info-box-content\">
<div>
";
// line 434
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("stockQuantity", [], "messages");
yield ":
<strong id=\"info-stock\"></strong>
</div>
<div>
";
// line 438
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("averageCost", [], "messages");
yield ":
<strong id=\"info-cost\"></strong>
</div>
</div>
</div>
</div>
</div>
<div class=\"card-body\" style=\"padding: 0\">
<div class=\"table-responsive\">
<table class=\"table modern-table\" id=\"product-list-table\" style=\"width: 100%;\">
<thead>
<tr>
<th style=\"width: 5%;\">";
// line 451
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("info", [], "messages");
yield "</th>
<th style=\"width: 20%;\">";
// line 452
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("product", [], "messages");
yield "</th>
<th style=\"width: 20%;\">";
// line 453
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("quantity", [], "messages");
yield "</th>
<th style=\"width: 15%;\">";
// line 454
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("unitPrice", [], "messages");
yield "</th>
<th style=\"width: 10%;\">";
// line 455
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("discount", [], "messages");
yield " (%)</th>
<th style=\"width: 10%;\">TVA (%)</th>
<th style=\"width: 15%; text-align: left !important;\" class=\"text-right\">";
// line 457
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("total", [], "messages");
yield "</th>
<th style=\"width: 15%;\" class=\"text-center\">";
// line 458
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("action", [], "messages");
yield "</th>
</tr>
</thead>
<tbody id=\"product-list-table-tbody\">
</tbody>
<tbody id=\"adder-body\">
<tr id=\"adder-row\">
<td>
<button type=\"button\" id=\"show-info-btn\"
class=\"btn btn-sm btn-info btn-icon\" title=\"";
// line 468
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("productInfo"), "html", null, true);
yield "\">
<i class=\"fas fa-info-circle\"></i>
</button>
</td>
<td class=\"product-select-cell\">
<select id=\"productselect\" class=\"form form-control\">
<option value=\"\">";
// line 474
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("selectWarehouseFirst"), "html", null, true);
yield "</option>
</select>
</td>
<td>
<div class=\"input-group input-group-sm\" style=\"max-width: 220px;\">
<input type=\"text\" id=\"adder-allocated-quantity\" class=\"form-control\" value=\"0\" min=\"0\">
<div class=\"input-group-append\">
<select id=\"adder-quantity-unit-select\" class=\"custom-select custom-select-sm\">
<option value=\"\">-</option>
</select>
</div>
</div>
</td>
<td><input type=\"text\" id=\"adder-price\" value=\"0\" min=\"0\"
class=\"form-control form-control-sm price-mask\" placeholder=\"Fiyat\">
</td>
<td>
<select name=\"adder-discount-select\" id=\"adder-discount-select\"
class=\"form-control form-control-sm\">
<option value=\"0\">% 0</option>
<option value=\"1\">% 1</option>
<option value=\"2\">% 2</option>
<option value=\"3\">% 3</option>
<option value=\"4\">% 4</option>
<option value=\"5\">% 5</option>
<option value=\"20\">% 20</option>
<option value=\"30\">% 30</option>
<option value=\"100\">% 100</option>
</select>
</td>
<td>
<select name=\"adder-tva-select\" id=\"adder-tva-select\"
class=\"form-control form-control-sm\">
<option value=\"0\" disabled>% 0</option>
<option value=\"10\">% 10</option>
<option value=\"20\">% 20</option>
</select>
</td>
<td class=\"text-right font-weight-bold small\">
<input name=\"adder-total\" id=\"adder-total\" type=\"number\"
class=\"form-control form-control-sm\"
placeholder=\"";
// line 515
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("total", [], "messages");
yield "\"/>
</td>
<td class=\"text-center\">
<div class=\"btn-group\" role=\"group\">
<button type=\"button\" id=\"open-unallocated-modal\" class=\"btn btn-outline-secondary btn-sm\" title=\"";
// line 520
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("unAllocatedQuantity", [], "messages");
yield "\">
<i class=\"fas fa-box-open\"></i>
<span id=\"unalloc-display\" class=\"badge badge-light ml-1\">0</span>
</button>
<button type=\"button\" id=\"add-product-row-btn\"
class=\"btn btn-sm btn-success btn-icon\">
<i class=\"fas fa-plus\"></i>
</button>
</div>
<input type=\"number\" id=\"adder-unallocated-quantity\" class=\"form-control d-none\" value=\"0\" min=\"0\">
<select id=\"adder-unallocated-quantity-unit-select\" class=\"custom-select custom-select-sm d-none\">
<option value=\"\">-</option>
</select>
</td>
</tr>
<tr id=\"info-details-row\">
<td colspan=\"8\" style=\"padding: 0; border-top: none;\">
<div id=\"info-details-content\" class=\"p-3\" style=\"display: none;\">
</div>
</td>
</tr>
</tbody>
<tfoot>
<!-- Rows will be injected by updateTotalAmounts() -->
<tr class=\"summary-row-placeholder\">
<td colspan=\"8\"></td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
";
// line 553
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["form"]) || array_key_exists("form", $context) ? $context["form"] : (function () { throw new RuntimeError('Variable "form" does not exist.', 553, $this->source); })()), 'form_end');
yield "
</div>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"add-customer-modal\" role=\"dialog\" aria-labelledby=\"customerModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"customerModalLabel\">";
// line 563
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("addCustomer", [], "messages");
yield "</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
";
// line 568
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["customerForm"]) || array_key_exists("customerForm", $context) ? $context["customerForm"] : (function () { throw new RuntimeError('Variable "customerForm" does not exist.', 568, $this->source); })()), 'form_start');
yield "
<div class=\"modal-body\">
";
// line 570
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["customerForm"]) || array_key_exists("customerForm", $context) ? $context["customerForm"] : (function () { throw new RuntimeError('Variable "customerForm" does not exist.', 570, $this->source); })()), 'widget');
yield "
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">";
// line 573
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("cancel", [], "messages");
yield "</button>
<button type=\"submit\" id=\"btn-customer-save\" class=\"btn btn-primary\">";
// line 574
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("save", [], "messages");
yield "</button>
</div>
";
// line 576
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["customerForm"]) || array_key_exists("customerForm", $context) ? $context["customerForm"] : (function () { throw new RuntimeError('Variable "customerForm" does not exist.', 576, $this->source); })()), 'form_end');
yield "
</div>
</div>
</div>
<div class=\"modal fade\" id=\"unallocatedModal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"unallocatedModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"unallocatedModalLabel\">";
// line 585
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("unAllocatedQuantity", [], "messages");
yield "</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
<div class=\"form-group\">
<label for=\"modal-unallocated-quantity\">";
// line 592
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("quantity", [], "messages");
yield "</label>
<input type=\"text\" id=\"modal-unallocated-quantity\" class=\"form-control mask-money\" value=\"0\">
</div>
<div class=\"form-group\">
<label for=\"modal-unallocated-unit\">";
// line 596
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("unit", [], "messages");
yield "</label>
<select id=\"modal-unallocated-unit\" class=\"custom-select\">
<option value=\"\">-</option>
</select>
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">";
// line 603
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("close", [], "messages");
yield "</button>
<button type=\"button\" id=\"save-unallocated-btn\" class=\"btn btn-primary\">";
// line 604
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("save", [], "messages");
yield "</button>
</div>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"add-payment-modal\" role=\"dialog\" aria-labelledby=\"paymentModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"paymentModalLabel\">";
// line 614
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("addPayment", [], "messages");
yield "</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
";
// line 620
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->searchAndRenderBlock((isset($context["paymentForm"]) || array_key_exists("paymentForm", $context) ? $context["paymentForm"] : (function () { throw new RuntimeError('Variable "paymentForm" does not exist.', 620, $this->source); })()), 'widget');
yield "
";
// line 621
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["paymentForm"]) || array_key_exists("paymentForm", $context) ? $context["paymentForm"] : (function () { throw new RuntimeError('Variable "paymentForm" does not exist.', 621, $this->source); })()), 'form_start');
yield "
";
// line 622
yield $this->env->getRuntime('Symfony\Component\Form\FormRenderer')->renderBlock((isset($context["paymentForm"]) || array_key_exists("paymentForm", $context) ? $context["paymentForm"] : (function () { throw new RuntimeError('Variable "paymentForm" does not exist.', 622, $this->source); })()), 'form_end');
yield "
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">";
// line 625
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("cancel", [], "messages");
yield "</button>
<button type=\"button\" id=\"save-payment-btn\" class=\"btn btn-primary\">";
// line 626
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("save", [], "messages");
yield "</button>
</div>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"updateCustomerModal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"updateModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"updateModalLabel\">";
// line 636
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("updateCustomerInfoMessage", [], "messages");
yield "</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<form id=\"updateCustomerForm\">
<div class=\"modal-body\">
<input type=\"hidden\" id=\"update_customer_id\">
<div class=\"form-group\">
<label for=\"update_customer_fullName\">";
// line 645
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("fullName", [], "messages");
yield "</label>
<input type=\"text\" class=\"form-control\" id=\"update_customer_fullName\" required>
</div>
<div class=\"form-group\">
<label for=\"update_customer_email\">";
// line 649
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("email", [], "messages");
yield "</label>
<input type=\"email\" class=\"form-control\" id=\"update_customer_email\">
</div>
<div class=\"form-group\">
<label for=\"update_customer_phone\">";
// line 653
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("phone", [], "messages");
yield "</label>
<input type=\"text\" class=\"form-control\" id=\"update_customer_phone\">
</div>
<div class=\"form-group\">
<label for=\"update_customer_address\">";
// line 657
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("address", [], "messages");
yield "</label>
<textarea class=\"form-control\" id=\"update_customer_address\" rows=\"3\"></textarea>
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">";
// line 662
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("close", [], "messages");
yield "</button>
<button type=\"submit\" class=\"btn btn-primary\">";
// line 663
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("save", [], "messages");
yield "</button>
</div>
</form>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"updateServiceModal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"updateServiceModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"updateServiceModalLabel\">";
// line 675
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("updateServiceInfoMessage", [], "messages");
yield "</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<form id=\"updateServiceForm\" onsubmit=\"return false;\">
<div class=\"modal-body\">
<input type=\"hidden\" id=\"update_service_id\">
<div class=\"form-group\">
<label for=\"update_service_name\">";
// line 684
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("serviceName", [], "messages");
yield "</label>
<input type=\"text\" class=\"form-control\" id=\"update_service_name\">
</div>
<div class=\"form-group\">
<label for=\"update_service_cost\">";
// line 688
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("cost", [], "messages");
yield "</label>
<input type=\"text\" value=\"0.00\" class=\"form-control mask-money\" id=\"update_service_cost\" required>
</div>
<div class=\"form-group\">
<label for=\"update_service_description\">";
// line 693
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("description", [], "messages");
yield "</label>
<input type=\"text\" class=\"form-control\" id=\"update_service_description\">
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">";
// line 698
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("close", [], "messages");
yield "</button>
<button type=\"button\" class=\"btn btn-primary\" id=\"service_modal_save_changes_btn\">";
// line 699
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("save", [], "messages");
yield "</button>
</div>
</form>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"autoPaymentModal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"autoPaymentModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog modal-dialog-centered\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"autoPaymentModalLabel\">";
// line 710
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("autoPaymentConfirmation"), "html", null, true);
yield "</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
<p id=\"auto-payment-message\" class=\"mb-3\"></p>
<div class=\"border rounded px-3 py-2 bg-light\">
<div class=\"d-flex justify-content-between\"><span>";
// line 718
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("salesTotal"), "html", null, true);
yield "</span><span id=\"auto-payment-sale-total\">0,00 ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("currency.symbol"), "html", null, true);
yield "</span></div>
<div class=\"d-flex justify-content-between\"><span>";
// line 719
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("currentPayments"), "html", null, true);
yield "</span><span id=\"auto-payment-current-payments\">0,00 ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("currency.symbol"), "html", null, true);
yield "</span></div>
<div class=\"d-flex justify-content-between font-weight-bold\"><span>";
// line 720
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("paymentToAdd"), "html", null, true);
yield "</span><span id=\"auto-payment-amount\">0,00 ";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("currency.symbol"), "html", null, true);
yield "</span></div>
<hr>
<div class=\"small\">
<div><strong>";
// line 723
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("paymentStatusLabel"), "html", null, true);
yield ":</strong> <span id=\"auto-payment-status\"></span></div>
<div><strong>";
// line 724
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("paymentTypeLabel"), "html", null, true);
yield ":</strong> <span id=\"auto-payment-method\"></span></div>
<div><strong>";
// line 725
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("description"), "html", null, true);
yield ":</strong> <span id=\"auto-payment-description\"></span></div>
</div>
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">";
// line 730
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("cancel"), "html", null, true);
yield "</button>
<button type=\"button\" class=\"btn btn-primary\" id=\"confirm-auto-payment\">";
// line 731
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("confirmAction"), "html", null, true);
yield "</button>
</div>
</div>
</div>
</div>
<!-- Hediye Çeki Kullanım Modalı -->
<div class=\"modal fade\" id=\"gift-voucher-usage-modal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"giftVoucherUsageModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog modal-dialog-centered\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"giftVoucherUsageModalLabel\">";
// line 742
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("giftVoucherUsage"), "html", null, true);
yield "</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
<div class=\"alert alert-info\">
<strong><span id=\"voucher-customer-name\"></span></strong>";
// line 749
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("voucherBalanceInfoSuffix"), "html", null, true);
yield ": <strong id=\"voucher-total-balance\">0.00 TL</strong>
</div>
<div class=\"form-group\">
<label for=\"voucher-use-amount\">";
// line 752
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("amountToUse"), "html", null, true);
yield "</label>
<input type=\"number\" id=\"voucher-use-amount\" class=\"form-control\" placeholder=\"0.00\" min=\"0\" step=\"0.01\">
<small class=\"form-text text-muted\">";
// line 754
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("enterAmountToUse"), "html", null, true);
yield "</small>
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">";
// line 758
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("cancel"), "html", null, true);
yield "</button>
<button type=\"button\" class=\"btn btn-primary\" id=\"btn-confirm-voucher-usage\">";
// line 759
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("confirmAndAdd"), "html", null, true);
yield "</button>
</div>
</div>
</div>
</div>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 767
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_javascript(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "javascript"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "javascript"));
// line 768
yield "<script src=\"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("/decimal.js"), "html", null, true);
yield "\"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const saleDateInput = document.getElementById('sales_form_salesDate');
if (saleDateInput && !saleDateInput.value) {
const now = new Date();
const localDate = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().split('T')[0];
saleDateInput.value = localDate;
saleDateInput.dispatchEvent(new Event('change', { bubbles: true }));
}
});
\$(document).ready(function() {
applyMasks();
const isDuplicatedProforma = ";
// line 783
yield (((array_key_exists("sales", $context) && !(null === CoreExtension::getAttribute($this->env, $this->source, (isset($context["sales"]) || array_key_exists("sales", $context) ? $context["sales"] : (function () { throw new RuntimeError('Variable "sales" does not exist.', 783, $this->source); })()), "id", [], "any", false, false, false, 783)))) ? ("true") : ("false"));
yield ";
const duplicatedSoldItems = (function(){
if(!isDuplicatedProforma){ return []; }
const items = [];
";
// line 788
if ((array_key_exists("sales", $context) && CoreExtension::getAttribute($this->env, $this->source, ($context["sales"] ?? null), "productsSolds", [], "any", true, true, false, 788))) {
// line 789
yield " ";
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(CoreExtension::getAttribute($this->env, $this->source, (isset($context["sales"]) || array_key_exists("sales", $context) ? $context["sales"] : (function () { throw new RuntimeError('Variable "sales" does not exist.', 789, $this->source); })()), "productsSolds", [], "any", false, false, false, 789));
foreach ($context['_seq'] as $context["_key"] => $context["ps"]) {
// line 790
yield " items.push({
productid: ";
// line 791
yield (((($tmp = CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 791)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 791), "id", [], "any", false, false, false, 791), "html", null, true)) : (0));
yield ",
productName: '";
// line 792
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(((CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "productName", [], "any", true, true, false, 792)) ? (Twig\Extension\CoreExtension::default(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "productName", [], "any", false, false, false, 792), (((($tmp = CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 792)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? (CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 792), "name", [], "any", false, false, false, 792)) : ("")))) : ((((($tmp = CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 792)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? (CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 792), "name", [], "any", false, false, false, 792)) : ("")))), "js"), "html", null, true);
yield "',
code: '";
// line 793
yield (((($tmp = CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 793)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 793), "code", [], "any", false, false, false, 793), "js"), "html", null, true)) : (""));
yield "',
measurement: '";
// line 794
yield (((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "measurement", [], "any", false, false, false, 794))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "measurement", [], "any", false, false, false, 794), "js"), "html", null, true)) : ((((CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 794) && CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 794), "measurement", [], "any", false, false, false, 794))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 794), "measurement", [], "any", false, false, false, 794), "name", [], "any", false, false, false, 794), "js"), "html", null, true)) : (""))));
yield "',
quantity: ";
// line 795
yield (((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "quantity", [], "any", false, false, false, 795))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "quantity", [], "any", false, false, false, 795), 2, ".", ""), "html", null, true)) : ("0"));
yield ",
unAllocatedQuantity: ";
// line 796
yield (((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "unAllocatedQuantity", [], "any", false, false, false, 796))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "unAllocatedQuantity", [], "any", false, false, false, 796), 2, ".", ""), "html", null, true)) : ("0"));
yield ",
unitPrice: ";
// line 797
yield (((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalUnitPurchasePrice", [], "any", false, false, false, 797))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalUnitPurchasePrice", [], "any", false, false, false, 797), 2, ".", ""), "html", null, true)) : ("0"));
yield ",
tax: ";
// line 798
yield (((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "tax", [], "any", false, false, false, 798))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "tax", [], "any", false, false, false, 798), 2, ".", ""), "html", null, true)) : ("0"));
yield ",
discount: ";
// line 799
yield (((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "discount", [], "any", false, false, false, 799))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "discount", [], "any", false, false, false, 799), 2, ".", ""), "html", null, true)) : ("0"));
yield ",
totalPurchasePrice: ";
// line 800
yield (((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalPuchasePrice", [], "any", false, false, false, 800))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalPuchasePrice", [], "any", false, false, false, 800), 2, ".", ""), "html", null, true)) : ((((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalPuchasePrice", [], "any", false, false, false, 800))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalPuchasePrice", [], "any", false, false, false, 800), 2, ".", ""), "html", null, true)) : ((((($tmp = !(null === CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalPrice", [], "any", false, false, false, 800))) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalPrice", [], "any", false, false, false, 800), 2, ".", ""), "html", null, true)) : ("0"))))));
yield ",
totalPrice: ";
// line 801
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "totalPrice", [], "any", false, false, false, 801), "html", null, true);
yield ",
warehouse: ";
// line 802
yield (((($tmp = CoreExtension::getAttribute($this->env, $this->source, (isset($context["sales"]) || array_key_exists("sales", $context) ? $context["sales"] : (function () { throw new RuntimeError('Variable "sales" does not exist.', 802, $this->source); })()), "warehouse", [], "any", false, false, false, 802)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["sales"]) || array_key_exists("sales", $context) ? $context["sales"] : (function () { throw new RuntimeError('Variable "sales" does not exist.', 802, $this->source); })()), "warehouse", [], "any", false, false, false, 802), "id", [], "any", false, false, false, 802), "html", null, true)) : (0));
yield ",
thickness: 0,
width: 0,
height: 0,
cost: 0,
productType: '";
// line 807
yield (((CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 807) && CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 807), "productTypeEnum", [], "any", false, false, false, 807))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "product", [], "any", false, false, false, 807), "productTypeEnum", [], "any", false, false, false, 807), "name", [], "any", false, false, false, 807), "js"), "html", null, true)) : (""));
yield "',
selectedUnitId: ";
// line 808
yield (((($tmp = CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "selectedUnit", [], "any", false, false, false, 808)) && $tmp instanceof Markup ? (string) $tmp : $tmp)) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, $context["ps"], "selectedUnit", [], "any", false, false, false, 808), "id", [], "any", false, false, false, 808), "html", null, true)) : ("null"));
yield "
});
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['ps'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 811
yield " ";
}
// line 812
yield " return items;
})();
// TODO: Duplicated items
async function importDuplicatedSoldItemsIfNeeded(){
await getProductsFromWarehouse();
if(!isDuplicatedProforma || duplicatedSoldItems.length === 0){
return;
}
const targetWarehouseId = ";
// line 820
yield (((array_key_exists("sales", $context) && CoreExtension::getAttribute($this->env, $this->source, (isset($context["sales"]) || array_key_exists("sales", $context) ? $context["sales"] : (function () { throw new RuntimeError('Variable "sales" does not exist.', 820, $this->source); })()), "warehouse", [], "any", false, false, false, 820))) ? ($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, CoreExtension::getAttribute($this->env, $this->source, (isset($context["sales"]) || array_key_exists("sales", $context) ? $context["sales"] : (function () { throw new RuntimeError('Variable "sales" does not exist.', 820, $this->source); })()), "warehouse", [], "any", false, false, false, 820), "id", [], "any", false, false, false, 820), "html", null, true)) : (0));
yield ";
if(targetWarehouseId){
\$('#warehouseselect').val(String(targetWarehouseId));
\$('#warehouseselect').trigger('change');
}
const start = Date.now();
const timeoutMs = 15000;
const timer = setInterval(function(){
if(Array.isArray(products) && products.length > 0){
clearInterval(timer);
try{
duplicatedSoldItems.forEach(function(item){
const p = products.find(function(prod){ return prod.productid === item.productid; });
if(p){
selectProduct(p.productid);
}
addProductToSoldList(
item.productid,
item.productName,
item.code,
item.measurement,
item.quantity,
item.unitPrice,
item.tax,
item.discount,
item.totalPurchasePrice,
item.warehouse,
item.unAllocatedQuantity,
item.thickness,
item.width,
item.height,
item.cost,
item.productType,
item.selectedUnitId
);
});
fetchSoldListRows();
updateTotalAmounts();
\$('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
fetchPaymentForm();
fetchStocks();
}catch(e){
console.error('Duplicate import error', e);
}
}else if(Date.now() - start > timeoutMs){
clearInterval(timer);
console.warn('Timed out waiting for products to load');
}
}, 200);
}
if(isDuplicatedProforma){
importDuplicatedSoldItemsIfNeeded();
}
// =============================================================================
// INITIALIZATION & GLOBAL VARIABLES
// =============================================================================
// Collapse sidebar only on this page
\$('body').addClass('sidebar-toggled');
\$('#accordionSidebar').addClass('toggled');
// Product data structure
let product = {
code: \"\",
id: 0,
length: 0,
measurement: \"\",
name: \"\",
priceFob: 0,
priceNavlun: 0,
productid: 0,
purchaseTotalAmount: 0,
quantity: 0,
thickness: 0,
totalQuantity: 0,
totalUnitPrice: 0,
width: 0,
cost: 0,
measurementUnit: \"\",
stock: 0,
warehouseid: 0,
selected: false,
}
let products = [];
// Sold product data structure
let soldProduct = {
productid: 0,
productName: \"\",
code: \"\",
measurement: \"\",
quantity: 0,
baseUnitQuantity: 0,
unAllocatedQuantity: 0,
unitPrice: 0,
tax: 0,
discount: 0,
totalPurchasePrice: 0,
warehouse: 0,
}
let soldList = [];
const MONEY_ROUNDING_MODE = Decimal.ROUND_HALF_UP;
// Payment data structure
let payment = {
uuid: 0,
id: 0,
amount: 0,
status: 0,
paymentDate: new Date(),
paymentDueDate: new Date(),
paymentMethod: 0,
description: 0,
}
let payments = []
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
function formatCurrency(amount) {
if (amount instanceof Decimal) {
amount = amount.toNumber();
}
return addCommas(amount.toFixed(2)) + ' ";
// line 949
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("currency.symbol"), "html", null, true);
yield "';
}
// Convert entered quantity in selected unit to product base unit quantity
function computeBaseUnitQuantity(productId, selectedUnitId, quantity, onDone){
try{
const product = products.find(p => p.productid === productId);
if(!product){ onDone(quantity); return; }
// If unit not selected or already base unit, fallback to same quantity
if(!selectedUnitId || String(selectedUnitId) === '' || String(selectedUnitId) === String(product.measurementUnitId)){
onDone(quantity);
return;
}
\$.ajax({
url: '/admin/measurement/convert-to-product-unit',
method: 'POST',
dataType: 'json',
data: {
productId: productId,
fromUnitId: selectedUnitId,
quantity: quantity
},
success: function(resp){
if(resp && resp.success){
onDone(parseFloat(resp.result));
} else {
onDone(quantity);
}
},
error: function(){ onDone(quantity); }
});
}catch(e){ onDone(quantity); }
}
// Check if product already exists in sold list with same parameters
function issetInSoldList(productid, quantity, unitPrice, tax, discount, totalPurchasePrice) {
const foundProduct = soldList.find(product =>
product.productid === productid &&
product.quantity === quantity &&
product.unitPrice === unitPrice &&
product.tax === tax &&
product.discount === discount &&
product.totalPurchasePrice === totalPurchasePrice
);
return !!foundProduct;
}
function applyMasks() {
\$('.mask-money').mask(\"##0.00\", { reverse: true });
}
// Convert various input types to Decimal for precise calculations
function convertToDecimal(value) {
try {
if (value instanceof Decimal) {
return value;
}
if (value === undefined || value === null) {
return new Decimal(0);
}
if (typeof value === 'number') {
if (!isFinite(value) || isNaN(value)) {
return new Decimal(0);
}
return new Decimal(value);
}
if (typeof value === 'string') {
var sanitized = value.replace(/[^0-9.,-]/g, '').replace(/,/g, '').trim();
if (sanitized === '' || sanitized === '-' || sanitized === '.' || sanitized === '-.') {
return new Decimal(0);
}
return new Decimal(sanitized);
}
return new Decimal(0);
} catch (e) {
return new Decimal(0);
}
}
// Round value to specified decimal places (default 2)
function roundToDecimal(value, decimalPlaces = 2) {
const decimalValue = new Decimal(value);
const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_UP);
return roundedValue.toNumber();
}
// Round stock values down to prevent overselling
function roundStockToDecimal(value, decimalPlaces = 2) {
const decimalValue = new Decimal(value);
const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_DOWN);
return roundedValue.toNumber();
}
// Round to nearest cent for currency calculations
function roundToNearestCent(amount) {
return Math.round(amount * 100) / 100;
}
// Round to two decimal places
function roundToTwoDecimals(number) {
var factor = Math.pow(10, 2);
return (Math.round(number * factor) / factor).toFixed(2);
}
// Validate numeric input
function validate(s) {
var rgx = /^[0-9]*\\.?[0-9]*\$/;
return s.match(rgx);
}
// Convert string to float, handling commas
function convertToFloat(value) {
if (typeof value === \"string\") {
value = value.replace(',', '');
}
return parseFloat(value);
}
// Add thousand separators to numbers
function addCommas(number) {
let decimals = 2;
number = (number + '').replace(/[^0-9+\\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = ',';
var dec = '.';
var s = '';
var toFixedFix = function(n, prec) {
var k = Math.pow(10, prec);
return '' + (Math.round(n * k) / k).toFixed(prec);
};
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (s[0].length > 3) {
s[0] = s[0].replace(/\\B(?=(?:\\d{3})+(?!\\d))/g, sep);
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
// Create popup window with specified dimensions
function createPopup(url, window_name = 'example-popup') {
var width = 1100;
var height = 700;
var screen_width = window.screen.width;
var screen_height = window.screen.height;
var popup_left = (screen_width - width) / 2;
var popup_top = (screen_height - height) / 2;
var popup_window = window.open(url, window_name, 'width=' + width + ',height=' + height + ',left=' + popup_left + ',top=' + popup_top);
}
// Show error alert using SweetAlert
function showErrorAlert(message) {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: '' + message,
background: 'white',
});
}
// Show success alert using SweetAlert
function showSuccessAlert(message) {
Swal.fire({
icon: 'success',
title: 'Başarılı',
text: '' + message,
background: 'white',
});
}
// =============================================================================
// PRODUCT MANAGEMENT FUNCTIONS
// =============================================================================
// Add product to sold list with validation
function addProductToSoldList(
productid,
productName,
code,
measurement,
quantity,
unitPrice,
tax,
discount,
totalPurchasePrice,
warehouseid,
unAllocatedQuantity,
thickness,
width,
height,
cost,
productType,
selectedUnitId = null
) {
var newQuantity = convertToDecimal(quantity).toNumber();
var newUnAllocatedQuantity = convertToDecimal(unAllocatedQuantity).toNumber();
var newPrice = convertToDecimal(unitPrice).toNumber();
var newTax = convertToDecimal(tax).toNumber();
var newDiscount = convertToDecimal(discount).toNumber();
var newTotalPurchasePrice = convertToDecimal(totalPurchasePrice).toNumber();
if (!issetInSoldList(productid, newQuantity, newPrice, newTax, newDiscount, newTotalPurchasePrice)) {
let productSold = {
uuid: crypto.randomUUID(),
productid: productid,
productName: productName,
code: code,
measurement: measurement,
quantity: newQuantity,
baseUnitQuantity: newQuantity,
unAllocatedQuantity: newUnAllocatedQuantity,
unitPrice: newPrice,
tax: newTax,
discount: newDiscount,
totalPurchasePrice: newTotalPurchasePrice,
warehouse: warehouseid,
thickness: thickness,
width: width,
height: height,
cost: cost,
productType: productType,
selectedUnitId: selectedUnitId,
}
soldList.push(productSold);
// compute base unit quantity asynchronously and update the item
computeBaseUnitQuantity(productid, selectedUnitId, newQuantity, function(baseQty){
const item = soldList.find(i => i.uuid === productSold.uuid);
if(item){ item.baseUnitQuantity = convertToDecimal(baseQty || newQuantity).toNumber(); }
});
fetchStocks();
fetchSoldListRows();
fetchPaymentForm();
\$('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
return productSold;
} else {
console.log(\"List + \")
console.log(soldList)
throw new Error(\"Ürün zaten listede mevcut !\")
}
}
// Remove product from sold list by UUID
function deleteProductInSoldList(uuid) {
soldList = soldList.filter(product =>
product.uuid !== uuid
);
fetchSoldListRows();
}
// Mark product as selected and unselect others
function selectProduct(productId) {
products.forEach(product => product.selected = false);
let product = products.find(product => product.productid === productId);
if (product) {
product.selected = true;
}
return products;
}
// Get currently selected product
function getSelectedProduct() {
return products.find(product => product.selected === true);
}
// Update product dropdown with available products
function updateProductSelect() {
\$('#productselect').empty();
\$('#productselect').append('<option value=\"0\" data-image=\"image-not-found.webp\">Ürün Seçin</option>');
\$.each(products, function(index, value) {
var image = value.image;
var option = '<option value=\"' + value.id + '\" data-image=\"' + image + '\" class=\"stockoption\">' + value.name + ' - ' + value.code + ' - ' + value.measurement + ' - ' + value.quantity + '</option>';
\$('#productselect').append(option);
});
\$('#productselect').attr('disabled', false);
}
// Load products from selected warehouse
function getProductsFromWarehouse() {
var id = \$('#warehouseselect option:selected').val();
if (!id) {
return;
}
\$.ajax({
url: \"/warehouse-stocks/\" + id,
dataType: 'JSON',
method: 'GET',
success: function(response) {
if (response.length > 0) {
if (soldList.length <= 0) {
products = [];
\$.each(response, function(index, product) {
let productObj = {
code: product.code,
id: product.id,
image: product.image,
length: 0,
measurement: product.measurement,
name: product.name,
priceFob: 0,
priceNavlun: 0,
productid: product.productid,
purchaseTotalAmount: 0,
quantity: product.totalQuantity,
unAllocatedQuantity: 0,
thickness: 0,
totalQuantity: product.totalQuantity,
totalUnitPrice: 0,
width: 0,
cost: 0,
measurementUnit: product.measurementUnit,
measurementUnitId: product.measurementUnitId,
stock: 0,
warehouseid: \$('#warehouseselect option:selected').val(),
convertibleUnits: [],
productType: product.productTypeEnum,
};
products.push(productObj);
});
updateProductSelect();
}
} else {
showErrorAlert('Seçmiş olduğunuz depoda hiç ürün stoğu bulunmamaktadır.');
}
},
error: function(error) {
showErrorAlert('Bilinmeyen bir sistem hatası oluştu lütfen tekrar deneyin.');
}
});
}
// Update stock quantities based on sold items
function fetchStocks() {
// const groupedSoldList = soldList.reduce((acc, soldProduct) => {
// if (!acc[soldProduct.productid]) {
// acc[soldProduct.productid] = 0;
// }
// acc[soldProduct.productid] += convertToDecimal(soldProduct.quantity).toNumber();
// return acc;
// }, {});
const groupedSoldList = soldList.reduce((acc, soldProduct) => {
const serviceTypes = [\"INSTALLATION_SERVICE\", \"MAINTENANCE_SERVICE\", \"CONSULTATION_SERVICE\", \"TREE\"];
const productTypeKey = soldProduct.productType ? (soldProduct.productType.key || soldProduct.productType.name) : null;
const isService = productTypeKey ? serviceTypes.includes(productTypeKey) : false;
if (isService) {
return acc;
}
if (!acc[soldProduct.productid]) {
acc[soldProduct.productid] = 0;
}
const quantityToDeduct = soldProduct.baseUnitQuantity || soldProduct.quantity;
acc[soldProduct.productid] += convertToDecimal(quantityToDeduct).toNumber();
return acc;
}, {});
products.forEach(product => {
if (groupedSoldList[product.productid]) {
const totalSoldQuantity = groupedSoldList[product.productid];
product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity - totalSoldQuantity).toNumber());
} else {
product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity).toNumber());
}
});
calculateAndWriteDiffBetweenPaymentAndSolds();
}
// Validate stock quantity before adding to sold list
// function checkStockQuantity(quantity) {
// let product = products.find(p => p.productid === parseInt(productId, 10));
// const productType = product.productType.key;
// if(productType === \"INSTALLATION_SERVICE\" || productType === \"MAINTENANCE_SERVICE\" || productType === \"CONSULTATION_SERVICE\" || productType === 'TREE'){
//
// \$('#updateServiceModal').modal('show');
//
// }else{
// if (convertToFloat(quantity) > product.quantity) {
// throw new Error('Yetersiz stok. Ekeleyebileceğiniz maksimum stok: ' + product.quantity);
// resetAddProductForm();
// }
// }
//
// }
function checkStockQuantity() {
return new Promise((resolve, reject) => {
const selectedProduct = getSelectedProduct();
if (!selectedProduct) {
return reject(new Error('Lütfen bir ürün seçin.'));
}
const productType = selectedProduct.productType.key;
const isService = [\"INSTALLATION_SERVICE\", \"MAINTENANCE_SERVICE\", \"CONSULTATION_SERVICE\", \"TREE\"].includes(productType);
if (isService) {
// Eğer ürün bir hizmet ise, stok kontrolü yapma, işlemi onayla.
return resolve();
}
const quantityStr = \$('#adder-allocated-quantity').val();
const fromUnitId = \$('#adder-quantity-unit-select').val();
const requestedQuantity = convertToDecimal(quantityStr).toNumber();
// Eğer birim seçilmemişse veya miktar 0 ise, miktar kadarını stokla karşılaştır.
if (!fromUnitId || fromUnitId === '') {
if (requestedQuantity > selectedProduct.quantity) {
return reject(new Error(`Yetersiz stok. Eklenebilecek maksimum miktar: \${selectedProduct.quantity} \${selectedProduct.measurementUnit}`));
}
return resolve(requestedQuantity); // Temel birimdeki miktarı döndür
}
// Seçilen birimdeki miktarı, ürünün temel birimine çevir.
\$.ajax({
url: '/admin/measurement/convert-to-product-unit',
method: 'POST',
dataType: 'json',
data: {
productId: selectedProduct.productid,
fromUnitId: fromUnitId,
quantity: requestedQuantity
},
success: function(resp) {
if (resp && resp.success) {
const requestedBaseQuantity = convertToDecimal(resp.result).toNumber();
// Temel birimdeki talep edilen miktar, stoktan büyük mü diye kontrol et.
if (requestedBaseQuantity > selectedProduct.quantity) {
reject(new Error(`Yetersiz stok. Talep edilen miktar (\${quantityStr} \${\$('#adder-quantity-unit-select option:selected').text()}), stoktaki \${selectedProduct.quantity} \${selectedProduct.measurementUnit}'den fazla.`));
} else {
resolve(requestedBaseQuantity); // Kontrol başarılı, temel birimdeki miktarı döndür.
}
} else {
reject(new Error('Birim dönüştürme sırasında bir hata oluştu. Stok kontrol edilemedi.'));
}
},
error: function() {
reject(new Error('Stok kontrolü sırasında sunucuya ulaşılamadı.'));
}
});
});
}
// Get product cost information from server
function getProductCost(pid) {
const url = '/product-cost/' + pid + '/' + getSelectedProduct().warehouseid;
\$.ajax({
url: \"/product-cost-detail/\" + pid + '?warehouse=' + getSelectedProduct().warehouseid,
method: \"POST\",
dataType: \"JSON\",
success: function(data) {
var product = getSelectedProduct();
product.cost = data.cost;
product.measurementUnit = data.measurement;
\$('.stock-info-span').html(data.stock.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" \" + data.measurement);
\$('.cost-info-span').html(data.cost.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" €\");
\$('.measurement-unit-info-span').html(data.measurement);
\$('#hidden-cost-input').val(data.cost);
const detailsHtml = `
<div class=\"row align-items-center\">
<div class=\"col-md-3\">\${product.name}</div>
<div class=\"col-md-3\">
<strong>
<button type=\"button\" class=\"btn btn-link btn-sm p-0 cost-popup-btn\" data-url=\"\${url}\">Maliyet: \${data.cost.toFixed(2)} €</button>
</strong>
</div>
<div class=\"col-md-3\"><strong>Stok:</strong> \${data.stock.toFixed(2)} \${data.measurement}</div>
<div class=\"col-md-3\"><strong>Birim:</strong> \${data.measurement}</div>
</div>
`;
\$('#info-details-content').html(detailsHtml);
return data;
},
error: function(data) {
\$('#info-details-content').html('<div class=\"text-danger\">Ürün maliyet bilgileri yüklenemedi.</div>');
}
});
}
// Generate HTML for a product row (Standard or AVOIR)
function generateProductRowHtml(value, options = {}) {
const isAvoir = options.isAvoir || false;
const readonly = isAvoir ? 'readonly disabled' : '';
const inputReadonly = isAvoir ? 'readonly' : ''; // For inputs we might want just readonly, not disabled
const rowClass = isAvoir ? 'bg-light text-danger' : '';
const uuid = value.uuid;
// Render Service Button (only for standard rows)
const serviceBtnHtml = !isAvoir ? renderServiceEditBtn(value) : '';
// Actions
let actionButtonsHtml = '';
if (!isAvoir) {
actionButtonsHtml = `
<div class=\"btn-group\" role=\"group\">
\${serviceBtnHtml}
<button type=\"button\" class=\"btn btn-outline-secondary btn-sm open-unallocated-modal-row\"
data-uuid=\"\${uuid}\" title=\"";
// line 1458
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("unAllocatedQuantity", [], "messages");
yield "\">
<i class=\"fas fa-box-open\"></i>
<span class=\"badge badge-light ml-1 unalloc-display\">\${value.unAllocatedQuantity.toFixed(2)}</span>
</button>
<button type=\"button\" class=\"btn btn-danger btn-sm delete-row-button\"
data-uuid=\"\${uuid}\" title=\"Sil\">
<i class=\"fas fa-trash\"></i>
</button>
</div>
`;
} else {
// Empty for AVOIR
actionButtonsHtml = '';
}
// Info Button
let infoButtonHtml = '';
if (!isAvoir) {
infoButtonHtml = `
<button type=\"button\" class=\"btn btn-sm btn-info btn-icon show-row-info-btn\"
data-uuid=\"\${uuid}\" title=\"Ürün Bilgileri\">
<i class=\"fas fa-info-circle\"></i>
</button>
`;
}
// Product Select / Name
let productSelectHtml = '';
if (isAvoir) {
// For AVOIR, we just show a static input or disabled select mimicking the look
productSelectHtml = `
<input type=\"text\" class=\"form-control text-danger font-weight-bold\" value=\"AVOIR\" readonly>
`;
} else {
let productOptions = '';
const serviceTypes = ['INSTALLATION_SERVICE','MAINTENANCE_SERVICE','CONSULTATION_SERVICE','TREE'];
const isServiceRow = (function(pt){
try {
if (!pt) return false;
if (typeof pt === 'string') return serviceTypes.includes(pt);
const key = pt.key || pt.name;
return serviceTypes.includes(key);
} catch(e) { return false; }
})(value.productType);
products.forEach(function(product) {
const isSelected = product.productid === value.productid ? 'selected' : '';
const displayName = (isSelected && isServiceRow && value.productName) ? value.productName : product.name;
productOptions += `<option value=\"\${product.productid}\" \${isSelected}>\${displayName} - \${product.measurement} - \${product.quantity} - \${product.code}</option>`;
});
productSelectHtml = `
<select name=\"productId[]\" class=\"form-control product-select-row\" data-uuid=\"\${uuid}\">
\${productOptions}
</select>
`;
}
// Unallocated Hidden Inputs
const unAllocatedHidens = !isAvoir ? `
<input type=\"hidden\" name=\"warehouseid[]\" value=\"\${value.warehouse}\">
<input type=\"hidden\" name=\"length[]\" value=\"\${value.length || ''}\">
<input type=\"hidden\" name=\"width[]\" value=\"\${value.width || ''}\">
<input type=\"hidden\" name=\"thickness[]\" value=\"\${value.thickness || ''}\">
<input type=\"hidden\" name=\"cost[]\" value=\"\${value.cost || ''}\">
<input type=\"hidden\" name=\"unAllocatedQuantity[]\" class=\"unallocated-quantity-hidden\" value=\"\${value.unAllocatedQuantity}\" data-uuid=\"\${uuid}\">
<input type=\"hidden\" name=\"unAllocatedQuantityUnit[]\" class=\"unallocated-unit-hidden\" value=\"\" data-uuid=\"\${uuid}\">
` : '';
// Quantity Unit Select
let quantityUnitSelectHtml = '';
if(isAvoir){
quantityUnitSelectHtml = `
<select class=\"custom-select custom-select-sm\" disabled>
<option>-</option>
</select>
`;
} else {
quantityUnitSelectHtml = `
<select class=\"custom-select custom-select-sm quantity-unit-select\"
name=\"quantityUnit[]\" data-uuid=\"\${uuid}\">
<option value=\"\">-</option>
</select>
`;
}
// Discount Options
let discountOptionsHtml = '';
const discounts = [0, 1, 2, 3, 4, 5, 20, 30, 100];
discounts.forEach(function(d){
discountOptionsHtml += `<option value=\"\${d}\" \${value.discount === d ? 'selected' : ''}>% \${d}</option>`;
});
// Tax Options
let taxOptionsHtml = '';
const taxes = [0, 10, 20];
taxes.forEach(function(t){
const disabledAttr = t === 0 ? 'disabled' : '';
taxOptionsHtml += `<option value=\"\${t}\" \${value.tax === t ? 'selected' : ''} \${disabledAttr}>% \${t}</option>`;
});
const quantityVal = value.quantity !== undefined ? value.quantity.toFixed(2) : '1.00';
const unitPriceVal = value.unitPrice !== undefined ? value.unitPrice.toFixed(2) : '0.00';
const totalPriceVal = value.totalPurchasePrice !== undefined ? value.totalPurchasePrice.toFixed(2) : '0.00';
// Only standard rows get the name=\"...\" attributes to be submitted
const nameAttr = isAvoir ? '' : 'name=';
return `
<tr id=\"\${uuid}\" data-product-id=\"\${value.productid || ''}\" class=\"\${rowClass}\">
<td>\${infoButtonHtml}</td>
<td class=\"product-select-cell\">
\${productSelectHtml}
\${unAllocatedHidens}
</td>
<td>
<div class=\"input-group input-group-sm\" style=\"max-width: 220px;\">
<input type=\"number\" \${!isAvoir ? 'name=\"quantity[]\"' : ''}
class=\"form-control quantity-input-row mask-money \${isAvoir ? 'text-danger' : ''}\"
value=\"\${quantityVal}\"
min=\"0\" step=\"0.01\" data-uuid=\"\${uuid}\" \${inputReadonly}>
<div class=\"input-group-append\">
\${quantityUnitSelectHtml}
</div>
</div>
</td>
<td>
<input type=\"text\" \${!isAvoir ? 'name=\"unitPrice[]\"' : ''}
class=\"form-control form-control-sm price-input-row mask-money \${isAvoir ? 'text-danger' : ''}\"
value=\"\${unitPriceVal}\"
min=\"0\" step=\"0.01\" data-uuid=\"\${uuid}\"
placeholder=\"Birim Fiyat\" \${inputReadonly}>
</td>
<td>
<select \${!isAvoir ? 'name=\"discount[]\"' : ''} class=\"form-control form-control-sm discount-select-row\"
data-uuid=\"\${uuid}\" \${readonly}>
\${discountOptionsHtml}
</select>
</td>
<td>
<select \${!isAvoir ? 'name=\"tax[]\"' : ''} class=\"form-control form-control-sm tax-select-row\"
data-uuid=\"\${uuid}\" \${readonly}>
\${taxOptionsHtml}
</select>
</td>
<td class=\"text-right\">
<input \${!isAvoir ? 'name=\"totalPrice[]\"' : ''} type=\"text\"
class=\"form-control form-control-sm total-input-row mask-money \${isAvoir ? 'text-danger font-weight-bold' : ''}\"
value=\"\${totalPriceVal}\"
data-uuid=\"\${uuid}\"
placeholder=\"Toplam\" \${inputReadonly}/>
</td>
<td class=\"text-center\">
\${actionButtonsHtml}
</td>
</tr>
\${!isAvoir ? `
<tr class=\"info-details-row-class\" data-uuid=\"\${uuid}\" style=\"display: none;\">
<td colspan=\"8\" style=\"padding: 0; border-top: none;\">
<div class=\"info-details-content-class p-3\" style=\"display: none;\">
<div class=\"row\">
<div class=\"col-md-3\"><strong>Ürün Adı:</strong> \${value.productName}</div>
<div class=\"col-md-3\"><strong>Kod:</strong> \${value.code}</div>
<div class=\"col-md-3\"><strong>Ölçü:</strong> \${value.measurement}</div>
<div class=\"col-md-3\"><strong>Uzunluk:</strong> \${value.length || '-'}</div>
</div>
<div class=\"row mt-2\">
<div class=\"col-md-3\"><strong>Genişlik:</strong> \${value.width || '-'}</div>
<div class=\"col-md-3\"><strong>Kalınlık:</strong> \${value.thickness || '-'}</div>
<div class=\"col-md-3\"><strong>Maliyet:</strong> \${(value.cost || 0).toFixed(2)} €</div>
<div class=\"col-md-3\"><strong>Depo:</strong> \${value.warehouse}</div>
</div>
</div>
</td>
</tr>` : ''}
`;
}
// Render sold products table rows with complete editing capabilities
// TODO: Seçilen ürün eğer hizmet ise dropdown da hizmet adını kaydedildiği şekilde göster
function fetchSoldListRows() {
\$('#product-list-table-tbody').empty();
soldList.forEach(function(value, index) {
const rowHtml = generateProductRowHtml(value, { isAvoir: false });
\$('#product-list-table-tbody').append(rowHtml);
\$(`.product-select-row[data-uuid=\"\${value.uuid}\"]`).select2({
theme: 'bootstrap4',
dropdownCssClass: 'custom-select-dropdown',
placeholder: 'Ürün Seçin',
allowClear: true
});
applyMasks();
// Load measurement units for each product row with default unit
const product = products.find(p => p.productid === value.productid);
const defaultUnitId = product ? product.measurementUnitId : null;
const preferredUnitId = (value.selectedUnitId !== undefined && value.selectedUnitId !== null && value.selectedUnitId !== '') ? value.selectedUnitId : defaultUnitId;
loadMeasurementUnitsForRow(value.productid, value.uuid, preferredUnitId);
});
// Check for Gift Voucher amount and add AVOIR row using reusable component
const totalVoucherAmount = calculateTotalGiftVoucherAmount();
if (!totalVoucherAmount.isZero()) {
const avoirItem = {
uuid: 'avoir-row',
productid: null,
productName: 'AVOIR',
quantity: 1,
unAllocatedQuantity: 0,
unitPrice: totalVoucherAmount.times(-1).toNumber(), // Negative unit price
totalPurchasePrice: totalVoucherAmount.times(-1).toNumber(), // Negative total
discount: 0,
tax: 0
};
// We override the visual display of the price to be positive in the input if desired,
// OR we keep it negative to indicate deduction.
// The user request screenshot showed \"AVOIR\" in red.
// Standard AVOIR usually implies negative context but let's stick to the styling.
// IMPORTANT: The screenshot usually shows the total as negative, so unit price should likely be negative too.
// Wait, if Quantity is 1 and Total is -Amount, Unit Price must be -Amount.
const avoirRowHtml = generateProductRowHtml(avoirItem, { isAvoir: true });
\$('#product-list-table-tbody').append(avoirRowHtml);
}
fetchStocks();
updateProductSelect();
updateTotalAmounts();
\$('#warehouseselect').attr('disabled', soldList.length > 0);
}
// --- GIFT VOUCHER INTEGRATION ---
// Set default ID to 0 or a value that won't match if not found.
// Using Twig to find the ID.
const PAYMENT_METHOD_GIFT_VOUCHER_ID = (function() {
";
// line 1698
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable((isset($context["paymentMethods"]) || array_key_exists("paymentMethods", $context) ? $context["paymentMethods"] : (function () { throw new RuntimeError('Variable "paymentMethods" does not exist.', 1698, $this->source); })()));
foreach ($context['_seq'] as $context["_key"] => $context["pm"]) {
// line 1699
yield " ";
if (((CoreExtension::getAttribute($this->env, $this->source, $context["pm"], "name", [], "any", false, false, false, 1699) == "Hediye Çeki") || (CoreExtension::getAttribute($this->env, $this->source, $context["pm"], "name", [], "any", false, false, false, 1699) == "Gift Voucher"))) {
// line 1700
yield " return \"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["pm"], "id", [], "any", false, false, false, 1700), "html", null, true);
yield "\";
";
}
// line 1702
yield " ";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['pm'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 1703
yield " return null;
})();
function calculateTotalGiftVoucherAmount() {
let totalVoucher = new Decimal(0);
if (!PAYMENT_METHOD_GIFT_VOUCHER_ID) return totalVoucher;
payments.forEach(function(p) {
if (String(p.paymentMethod) === String(PAYMENT_METHOD_GIFT_VOUCHER_ID)) {
totalVoucher = totalVoucher.plus(convertToDecimal(p.amount));
}
});
return totalVoucher;
}
// ---------------------------------
function renderServiceEditBtn(value){
const product = products.find(p => p.productid === value.productid);
if (isProductService(product.productid)) {
return `
<button type=\"button\"
class=\"btn btn-warning btn-sm edit-service-button\"
data-uuid=\"\${value.uuid}\"
title=\"Servisi Güncelle\">
<i class=\"fas fa-edit\"></i>
</button>
`;
}
return ''; // değilse hiç ekleme
}
function calculateRowFinancials(source) {
const quantity = convertToDecimal(source.quantity || 0);
const unAllocatedQuantity = convertToDecimal(source.unAllocatedQuantity || 0);
const unitPrice = convertToDecimal(source.unitPrice || 0);
const discountPercentage = convertToDecimal(source.discount || 0);
const taxPercentage = convertToDecimal(source.tax || 0);
const totalQuantity = quantity.plus(unAllocatedQuantity);
if (totalQuantity.isZero()) {
return {
subtotal: new Decimal(0),
discountAmount: new Decimal(0),
afterDiscount: new Decimal(0),
taxAmount: new Decimal(0),
total: new Decimal(0),
totalRounded: new Decimal(0),
taxPercentage
};
}
const subtotal = totalQuantity.mul(unitPrice);
const discountAmount = subtotal.mul(discountPercentage.div(100));
const afterDiscount = subtotal.minus(discountAmount);
const taxAmount = afterDiscount.mul(taxPercentage.div(100));
const total = afterDiscount.plus(taxAmount);
return {
subtotal,
discountAmount,
afterDiscount,
taxAmount,
total,
totalRounded: total.toDecimalPlaces(2, MONEY_ROUNDING_MODE),
taxPercentage
};
}
function aggregateSoldTotals() {
const aggregation = {
grossTotal: new Decimal(0),
totalDiscount: new Decimal(0),
subtotal: new Decimal(0),
tax: new Decimal(0),
grandTotal: new Decimal(0),
byTaxRate: {}
};
soldList.forEach(function(item) {
// item.totalPurchasePrice'ı kullan (manuel veya otomatik hesaplanmış)
const itemTotal = convertToDecimal(item.totalPurchasePrice || 0);
// Toplam üzerinden geriye doğru hesaplama
// Formül: total = subtotal × (1 - discount%) × (1 + tax%)
// Ters formül: afterDiscount = total / (1 + tax%)
// subtotal = afterDiscount / (1 - discount%)
const taxPercentage = convertToDecimal(item.tax || 0);
const discountPercentage = convertToDecimal(item.discount || 0);
const taxMultiplier = new Decimal(1).plus(taxPercentage.div(100));
// Adım 1: KDV'yi çıkar → İndirim sonrası tutarı bul
// afterDiscount = total / (1 + tax%)
const afterDiscount = itemTotal.div(taxMultiplier);
// Adım 2: KDV tutarını hesapla
// taxAmount = afterDiscount × tax%
const taxAmount = afterDiscount.mul(taxPercentage.div(100));
// NOT: \"Ara Toplam\" (subtotal) = afterDiscount (indirim uygulanmış tutar)
// Çünkü görüntülenen \"Ara Toplam\" indirim sonrası, KDV öncesi tutar
// Calculate Gross (before discount)
const quantity = convertToDecimal(item.quantity).plus(convertToDecimal(item.unAllocatedQuantity));
const unitPrice = convertToDecimal(item.unitPrice);
const grossLine = quantity.mul(unitPrice);
// Calculate Discount derived from Gross vs Net
const discountAmount = grossLine.minus(afterDiscount);
aggregation.grossTotal = aggregation.grossTotal.plus(grossLine);
aggregation.totalDiscount = aggregation.totalDiscount.plus(discountAmount);
aggregation.subtotal = aggregation.subtotal.plus(afterDiscount);
aggregation.tax = aggregation.tax.plus(taxAmount);
aggregation.grandTotal = aggregation.grandTotal.plus(itemTotal);
const rateKey = taxPercentage.toFixed(4);
if (!aggregation.byTaxRate[rateKey]) {
aggregation.byTaxRate[rateKey] = {
rate: taxPercentage,
amount: new Decimal(0)
};
}
aggregation.byTaxRate[rateKey].amount = aggregation.byTaxRate[rateKey].amount.plus(taxAmount);
});
aggregation.subtotalRounded = aggregation.subtotal.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
aggregation.taxRounded = aggregation.tax.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
aggregation.grandTotalRounded = aggregation.grandTotal.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
return aggregation;
}
// Track which row is being updated to prevent circular updates
let rowUpdatingFromTotal = {};
let rowUpdatingFromUnitPrice = {};
// Track rows where total was manually entered (should not recalculate total)
let rowHasManualTotal = {};
function updateSoldProductFromRow(uuid) {
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex === -1) {
return;
}
// Eğer bu satır toplam tarafından güncelleniyorsa, tekrar toplam güncelleme
if(rowUpdatingFromTotal[uuid]) {
return;
}
rowUpdatingFromUnitPrice[uuid] = true;
const \$row = \$(`#\${uuid}`);
const quantity = convertToDecimal(\$row.find('.quantity-input-row').val()).toNumber();
const unitPrice = convertToDecimal(\$row.find('.price-input-row').val()).toNumber();
const discount = convertToDecimal(\$row.find('.discount-select-row').val()).toNumber();
const tax = convertToDecimal(\$row.find('.tax-select-row').val()).toNumber();
soldList[itemIndex].quantity = quantity;
soldList[itemIndex].unitPrice = unitPrice;
soldList[itemIndex].discount = discount;
soldList[itemIndex].tax = tax;
const selectedUnitId = \$row.find('.quantity-unit-select').val();
computeBaseUnitQuantity(soldList[itemIndex].productid, selectedUnitId, quantity, function(baseQty){
soldList[itemIndex].baseUnitQuantity = convertToDecimal(baseQty || quantity).toNumber();
});
// Eğer bu satırda manuel toplam girildiyse, toplam sabit kalmalı, sadece birim fiyat güncellensin
if (rowHasManualTotal[uuid]) {
// Manuel toplam korunuyor, birim fiyatı tersine hesapla
const manualTotal = convertToDecimal(soldList[itemIndex].totalPurchasePrice);
const totalQuantity = convertToDecimal(quantity).plus(convertToDecimal(soldList[itemIndex].unAllocatedQuantity || 0));
if (!totalQuantity.isZero()) {
const discountMultiplier = new Decimal(1).minus(convertToDecimal(discount).div(100));
const taxMultiplier = new Decimal(1).plus(convertToDecimal(tax).div(100));
if (discountMultiplier.isZero()) {
soldList[itemIndex].unitPrice = 0;
\$row.find('.price-input-row').val('0.00');
} else if (!taxMultiplier.isZero()) {
const calculatedUnitPrice = manualTotal
.div(taxMultiplier)
.div(discountMultiplier)
.div(totalQuantity)
.toDecimalPlaces(6, MONEY_ROUNDING_MODE);
soldList[itemIndex].unitPrice = calculatedUnitPrice.toNumber();
\$row.find('.price-input-row').val(calculatedUnitPrice.toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
}
}
// Toplam input'u DEĞİŞTİRME - manuel değer korunuyor
} else {
// Normal hesaplama - toplam otomatik hesaplansın
const rowTotals = calculateRowFinancials(soldList[itemIndex]);
soldList[itemIndex].totalPurchasePrice = rowTotals.totalRounded.toNumber();
\$row.find('.total-input-row').val(rowTotals.totalRounded.toFixed(2));
}
updateTotalAmounts();
fetchStocks();
fetchPaymentForm();
setTimeout(function(){ delete rowUpdatingFromUnitPrice[uuid]; }, 0);
}
// Toplam tutar değiştirildiğinde birim fiyatı tersine hesaplar
function updateUnitPriceFromTotal(uuid) {
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex === -1) {
return;
}
// Eğer bu satır birim fiyat tarafından güncelleniyorsa, döngüyü engelle
if(rowUpdatingFromUnitPrice[uuid]) {
return;
}
rowUpdatingFromTotal[uuid] = true;
// ÖNEMLİ: Toplam manuel girildi, bu satırı işaretle
rowHasManualTotal[uuid] = true;
const \$row = \$(`#\${uuid}`);
const totalValue = convertToDecimal(\$row.find('.total-input-row').val());
const item = soldList[itemIndex];
const totalQuantity = convertToDecimal(item.quantity || 0).plus(convertToDecimal(item.unAllocatedQuantity || 0));
if (totalQuantity.isZero()) {
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
return;
}
// Manuel girilen toplam değerini kaydet
soldList[itemIndex].totalPurchasePrice = totalValue.toNumber();
// Eğer toplam 0 ise, birim fiyat da 0 olmalı
if (totalValue.isZero()) {
\$row.find('.price-input-row').val('0.00');
soldList[itemIndex].unitPrice = 0;
updateTotalAmounts();
fetchStocks();
fetchPaymentForm();
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
return;
}
const discountMultiplier = new Decimal(1).minus(convertToDecimal(item.discount || 0).div(100));
const taxMultiplier = new Decimal(1).plus(convertToDecimal(item.tax || 0).div(100));
// %100 indirimde discountMultiplier = 0 olur, bu durumda birim fiyat 0 olmalı
if (discountMultiplier.isZero()) {
\$row.find('.price-input-row').val('0.00');
soldList[itemIndex].unitPrice = 0;
updateTotalAmounts();
fetchStocks();
fetchPaymentForm();
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
return;
}
if (taxMultiplier.isZero()) {
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
return;
}
// Formül: unitPrice = total / ((1 + tax%) * (1 - discount%) * quantity)
const unitPrice = totalValue
.div(taxMultiplier)
.div(discountMultiplier)
.div(totalQuantity)
.toDecimalPlaces(6, MONEY_ROUNDING_MODE);
\$row.find('.price-input-row').val(unitPrice.toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
soldList[itemIndex].unitPrice = unitPrice.toNumber();
// Sadece stok ve genel toplamları güncelle, satır toplamını değiştirme
updateTotalAmounts();
fetchStocks();
fetchPaymentForm();
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
}
// =============================================================================
// WAREHOUSE SELECTION EVENT HANDLER
// =============================================================================
// Handle warehouse selection change
\$('#warehouseselect').on('change', function() {
getProductsFromWarehouse();
});
// Auto-select warehouse if there is only one
var \$warehouseSelect = \$('#warehouseselect');
if (\$warehouseSelect.find('option').length === 2 && \$warehouseSelect.val() === \"\") {
var firstWarehouseVal = \$warehouseSelect.find('option').eq(1).val();
\$warehouseSelect.val(firstWarehouseVal).trigger('change');
}
// Load measurement units for a specific product row (with caching)
let measurementUnitsCache = {};
function loadMeasurementUnitsForRow(productId, uuid, defaultUnitId = null) {
// Check cache first
if (measurementUnitsCache[productId]) {
populateUnitSelectForRow(uuid, measurementUnitsCache[productId], defaultUnitId);
return;
}
// Load from server if not cached
\$.get('/product/' + productId + '/measurement-units', function(units) {
// Cache the units
measurementUnitsCache[productId] = units || [];
populateUnitSelectForRow(uuid, units, defaultUnitId);
}).fail(function() {
console.log('Failed to load measurement units for product:', productId);
measurementUnitsCache[productId] = []; // Cache empty array to prevent repeated requests
});
}
// Populate unit select dropdown for a specific row
function populateUnitSelectForRow(uuid, units, defaultUnitId = null) {
const \$select = \$(`.quantity-unit-select[data-uuid=\"\${uuid}\"]`);
\$select.empty();
if (Array.isArray(units) && units.length > 0) {
units.forEach(function(unit) {
const isSelected = defaultUnitId !== null && defaultUnitId !== undefined && defaultUnitId !== '' && String(unit.id) === String(defaultUnitId);
const selectedAttr = isSelected ? ' selected' : '';
\$select.append('<option value=\"' + unit.id + '\"' + selectedAttr + '>' + unit.symbol + '</option>');
});
if (units.length === 1) {
\$select.prop('disabled', true);
} else {
\$select.prop('disabled', false);
}
} else {
\$select.append('<option value=\"\">-</option>');
\$select.prop('disabled', false);
}
}
// Update total amounts in footer
function updateTotalAmounts() {
const taxLabelPrefix = \"TVA\";
const taxTotalLabel = \"";
// line 2058
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("total"), "js"), "html", null, true);
yield "\";
const formatTaxRate = function(rateDecimal) {
const rateNumber = convertToDecimal(rateDecimal || 0).toNumber();
if (!isFinite(rateNumber)) {
return \"0\";
}
if (Math.abs(rateNumber % 1) < 1e-6) {
return rateNumber.toFixed(0);
}
return rateNumber.toFixed(2).replace(/\\.?0+\$/, \"\");
};
const totals = aggregateSoldTotals();
const \$tfoot = \$(\"#product-list-table tfoot\");
\$tfoot.empty();
// Helper to create row
const addRow = (label, value, isBold=false, colorClass='') => {
const row = \$(`<tr class=\"summary-row \${isBold ? 'font-weight-bold' : ''} \${colorClass}\">
<td colspan=\"8\">
<div class=\"sale-summary-row\">
<span class=\"summary-label\">\${label}</span>
<span class=\"summary-value\">\${formatCurrency(value)}</span>
</div>
</td>
</tr>`);
\$tfoot.append(row);
};
// 1. Total H.T. (Gross)
addRow('Total H.T.:', totals.grossTotal, true);
// 2. Remise
if (!totals.totalDiscount.isZero() && totals.totalDiscount.toNumber() > 0.005) {
const \$row = \$(`<tr class=\"summary-row\">
<td colspan=\"8\">
<div class=\"sale-summary-row\">
<span class=\"summary-label\">Remise:</span>
<span class=\"summary-value\">\${formatCurrency(totals.totalDiscount.negated())}</span>
</div>
</td>
</tr>`);
\$tfoot.append(\$row);
}
// 3. Total H.T. (Net)
addRow('Total H.T.:', totals.subtotal, true);
// 4. AVOIR
const totalVoucherAmount = calculateTotalGiftVoucherAmount();
let finalTotal = totals.grandTotalRounded; // Assuming totals.grandTotalRounded is available in result? Yes
// Wait, aggregateSoldTotals returns grandTotalRounded.
// But calculateSaleSummary in edit.html.twig returned summary.roundedTotal.
if (!totalVoucherAmount.isZero()) {
const \$avoirRow = \$(`<tr class=\"summary-row text-danger font-weight-bold\">
<td colspan=\"8\">
<div class=\"sale-summary-row\">
<span class=\"summary-label\">AVOIR:</span>
<span class=\"summary-value\">-\${formatCurrency(totalVoucherAmount)}</span>
</div>
</td>
</tr>`);
\$tfoot.append(\$avoirRow);
finalTotal = finalTotal.minus(totalVoucherAmount);
}
// 5. TVA
const taxEntries = Object.values(totals.byTaxRate)
.filter(function(entry) { return !entry.amount.isZero(); })
.sort(function(a, b) { return a.rate.toNumber() - b.rate.toNumber(); });
if (taxEntries.length) {
taxEntries.forEach(function(entry) {
addRow(taxLabelPrefix + \" (\" + formatTaxRate(entry.rate) + \"%):\", entry.amount);
});
} else {
addRow(taxLabelPrefix + \" (0%):\", 0);
}
// 6. TOTAL
const \$totalRow = \$(`<tr class=\"total-row summary-row font-weight-bold\">
<td colspan=\"8\">
<div class=\"sale-summary-row summary-total\">
<span class=\"summary-label\">TOTAL À PAYER (EUR):</span>
<span class=\"summary-value display-5\">\${formatCurrency(finalTotal)}</span>
</div>
</td>
</tr>`);
\$tfoot.append(\$totalRow);
calculateAndWriteDiffBetweenPaymentAndSolds();
}
// Update sold list item by UUID
function updateSoldListItem(uuid, field, value) {
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex === -1) {
return;
}
soldList[itemIndex][field] = convertToDecimal(value).toNumber();
updateSoldProductFromRow(uuid);
}
// Satıştaki miktarları listeden düşme Un Allocated HARİÇ tutulur
function fetchStocks() {
// soldList'i productid'e göre grupla
const groupedSoldList = soldList.reduce((acc, soldProduct) => {
if (!acc[soldProduct.productid]) {
acc[soldProduct.productid] = 0;
}
acc[soldProduct.productid] += convertToDecimal(soldProduct.baseUnitQuantity).toNumber();
return acc;
}, {});
// products listesindeki totalQuantity'yi güncelle
products.forEach(product => {
if (groupedSoldList[product.productid]) {
const totalSoldQuantity = groupedSoldList[product.productid];
product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity - totalSoldQuantity).toNumber());
} else {
// Eğer soldList'te bu ürün yoksa, quantity aynı kalır
product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity).toNumber());
}
});
calculateAndWriteDiffBetweenPaymentAndSolds();
}
function getTotalSoldAmount() {
return aggregateSoldTotals().grandTotalRounded.toNumber();
}
\$(document).on('blur', '#payment-list-table-tbody .payment-amount-input', function() {
// Buraya kod eklenebilir
});
// Gelen değeri decimale çevirme
function convertToDecimal(value) {
try {
if (value instanceof Decimal) {
return value;
}
if (value === undefined || value === null) {
return new Decimal(0);
}
if (typeof value === 'number') {
if (!isFinite(value) || isNaN(value)) {
return new Decimal(0);
}
return new Decimal(value);
}
if (typeof value === 'string') {
// Keep digits, minus, dot and comma; strip others
var sanitized = value.replace(/[^0-9.,-]/g, '').replace(/,/g, '').trim();
if (sanitized === '' || sanitized === '-' || sanitized === '.' || sanitized === '-.') {
return new Decimal(0);
}
return new Decimal(sanitized);
}
return new Decimal(0);
} catch (e) {
return new Decimal(0);
}
}
// Seçilen ürünün birimini dropdown'da gösterme (handled by main product select handler)
function clearUnitSelects(){
\$('#adder-quantity-unit-select').empty().append('<option value=\"\">-</option>');
\$('#adder-unallocated-quantity-unit-select').empty().append('<option value=\"\">-</option>');
\$('#modal-unallocated-unit').empty().append('<option value=\"\">-</option>');
}
// Select içine çevrilebilir birimleri koyuyor
function populateSelectWithUnits(\$select, units) {
\$select.empty();
if (Array.isArray(units) && units.length > 0) {
units.forEach(function(unit) {
\$select.append('<option value=\"' + unit.id + '\">' + unit.symbol + '</option>');
});
if (units.length === 1) {
\$select.prop('disabled', true);
} else {
\$select.prop('disabled', false);
}
} else {
\$select.append('<option value=\"\">-</option>');
\$select.prop('disabled', false);
}
}
// Ürünün çevrilebilir birimlerini select içine koyuyoruz.
function refreshUnitSelectsForProduct(product) {
if (!product || !product.convertibleUnits) {
clearUnitSelects();
return;
}
populateSelectWithUnits(\$('#adder-quantity-unit-select'), product.convertibleUnits);
populateSelectWithUnits(\$('#adder-unallocated-quantity-unit-select'), product.convertibleUnits);
populateSelectWithUnits(\$('#modal-unallocated-unit'), product.convertibleUnits);
}
// TODO: Birimler doldurulurken ilgili birimin karşılığındaki miktar yazılacak
function fillUnitSelects(product){
// Check cache first
if (measurementUnitsCache[product.productid]) {
product.convertibleUnits = measurementUnitsCache[product.productid];
refreshUnitSelectsForProduct(product);
computeAndShowBaseUnitEquivalent();
return;
}
\$.get('/product/' + product.productid + '/measurement-units', function(units) {
// Cache the units
measurementUnitsCache[product.productid] = units || [];
// Store in product object
product.convertibleUnits = units || [];
// Update selects
refreshUnitSelectsForProduct(product);
// Set default unit as selected
if (product.measurementUnitId) {
\$('#adder-quantity-unit-select').val(product.measurementUnitId);
\$('#adder-unallocated-quantity-unit-select').val(product.measurementUnitId);
\$('#modal-unallocated-unit').val(product.measurementUnitId);
}
computeAndShowBaseUnitEquivalent();
});
}
// 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
function computeAndShowBaseUnitEquivalent() {
try {
const selectedProduct = getSelectedProduct();
if (!selectedProduct || !selectedProduct.productid) {
updateAllocatedQuantityHint('');
return;
}
const fromUnitId = \$('#adder-quantity-unit-select').val();
const qtyStr = \$('#adder-allocated-quantity').val();
// If base unit is selected, do not show duplicate label
if (fromUnitId && String(fromUnitId) === String(selectedProduct.measurementUnitId)) {
updateAllocatedQuantityHint('');
return;
}
if (!fromUnitId || fromUnitId === '' || qtyStr === '' || qtyStr === null) {
updateAllocatedQuantityHint('');
return;
}
const quantity = convertToDecimal(qtyStr).toNumber();
if (isNaN(quantity)) {
updateAllocatedQuantityHint('');
return;
}
\$.ajax({
url: '/admin/measurement/convert-to-product-unit',
method: 'POST',
dataType: 'json',
data: {
productId: selectedProduct.productid,
fromUnitId: fromUnitId,
quantity: quantity
},
success: function(resp) {
if (resp && resp.success) {
const unitText = selectedProduct.measurementUnit ? (' ' + selectedProduct.measurementUnit) : '';
updateAllocatedQuantityHint((Number(resp.result).toFixed(2)) + unitText);
} else {
updateAllocatedQuantityHint('');
}
},
error: function() {
updateAllocatedQuantityHint('');
}
});
} catch (e) {
updateAllocatedQuantityHint('');
}
}
// Base unit tipinden miktar.
function updateAllocatedQuantityHint(text) {
const \$group = \$('#adder-allocated-quantity').closest('.input-group');
if (\$('#allocated-quantity-converted-hint').length === 0) {
\$('<small id=\"allocated-quantity-converted-hint\" class=\"form-text text-muted\"></small>').insertAfter('#adder-quantity-unit-select');
}
\$('#allocated-quantity-converted-hint').text(text || '');
}
function roundToDecimal(value, decimalPlaces = 2) {
const decimalValue = new Decimal(value);
const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_UP);
return roundedValue.toNumber();
}
function roundStockToDecimal(value, decimalPlaces = 2) {
const decimalValue = new Decimal(value);
const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_DOWN);
return roundedValue.toNumber();
}
function selectProduct(productId) {
products.forEach(product => product.selected = false);
let product = products.find(product => product.productid === productId);
if (product) {
product.selected = true;
}
return products;
}
function getSelectedProduct() {
return products.find(product => product.selected === true);
}
// Ürün seçme selectini yeniden güncelleme.
function updateProductSelect() {
\$('#productselect').empty();
\$('#productselect').append('<option value=\"0\" data-image=\"image-not-found.webp\">Ürün Seçin</option>');
\$.each(products, function(index, value) {
var image = value.image;
var option = '<option value=\"' + value.id + '\" data-image=\"' + image + '\" class=\"stockoption\">' + value.name + ' - ' + value.code + ' - ' + value.measurement + ' - ' + value.quantity + '</option>';
\$('#productselect').append(option);
});
\$('#productselect').attr('disabled', false);
}
\$('#products_sold_quantity').mask(\"##0.00\", {
reverse: true
});
\$('#adder-allocated-quantity').mask(\"##0.00\", {
reverse: true
});
\$('#adder-unallocated-quantity').mask(\"##0.00\", {
reverse: true
});
\$('#adder-price').mask(\"##0.00\", {
reverse: true
});
\$('#adder-total').mask(\"##0.00\", {
reverse: true
});
\$('#products_sold_unitPriceFob').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_unitPriceNavlun').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_totalUnitPrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_totalUnitPurchasePrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_totalPrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_totalPuchasePrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#sales_form_totalPurchasePrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#sales_form_totalPurchasePrice').attr('readonly', 'readonly');
var totalQuantity = 0;
var productId = 0;
var productname = \$('#products_sold_productName');
var productmeasurement = \$('#products_sold_measurement');
var unitPriceFob = \$('#products_sold_unitPriceFob');
var unitPricenavlun = \$('#products_sold_unitPriceNavlun');
var totalUnitPrice = \$('#products_sold_totalUnitPrice');
// ÖDEME FORMU LİSTEYE EKLEME
\$('#save-payment-btn').on('click', function() {
let amount = roundToDecimal(convertToDecimal(\$('#payment_amount').val()));
let payment_paymentStatus = \$('#payment_paymentStatus').val();
let payment_paymentDate = \$('#payment_paymentDate').val();
let payment_dueDate = \$('#payment_dueDate').val();
let payment_paymentMethod = \$('#payment_paymentMethod').val();
let payment_description = \$('#payment_description').val();
if (amount <= 0 || !payment_paymentStatus || !payment_paymentDate || !payment_dueDate || !payment_paymentMethod || !payment_description) {
throw new Error(\"Formdaki eksikleri tamamlayın lütfen\")
}
payment = {
uuid: crypto.randomUUID(),
amount: amount,
status: payment_paymentStatus,
paymentDate: payment_paymentDate,
paymentDueDate: payment_dueDate,
paymentMethod: payment_paymentMethod,
description: payment_description,
voucherCode: \$('#payment_voucherCode').val()
}
payments.push(payment);
fetchPaymentForm();
// Trigger update to show AVOIR if needed
fetchSoldListRows();
});
// Ödeme satırını silme işlemi
\$(document).on('click', '.delete-payment', function() {
\$row = \$(this).closest('tr');
let id = \$row.attr(\"id\");
payments = payments.filter(payment => payment.uuid !== id);
fetchPaymentForm();
// Trigger update to remove AVOIR if needed
fetchSoldListRows();
});
// Ödeme formunu güncelleme
function fetchPaymentForm() {
\$('#payment-list-table-tbody').empty();
payments.forEach(function(val) {
var newRow = `<tr id=\"\${val.uuid}\">
<td><input type=\"text\" class=\"form-control payment-amount payment-amount-input\" value=\"\${addCommas(val.amount) || ''}\" name=\"paymentAmount[]\" placeholder=\"Miktar\"></td>
<td>
<select class=\"form-control\" name=\"paymentStatus[]\">
<option value=\"0\" \${val.status === \"0\" ? 'selected' : ''}>";
// line 2492
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("payment_status.paid", [], "messages");
yield "</option>
<option value=\"1\" \${val.status === \"1\" ? 'selected' : ''}>";
// line 2493
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("payment_status.not_paid", [], "messages");
yield "</option>
<option value=\"2\" \${val.status === \"2\" ? 'selected' : ''}>";
// line 2494
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("payment_status.term_sale", [], "messages");
yield "</option>
</select>
</td>
<td><input name=\"paymentDate[]\" type=\"date\" class=\"form-control\" value=\"\${val.paymentDate || ''}\" required></td>
<td><input name=\"paymentDueDate[]\" type=\"date\" class=\"form-control\" value=\"\${val.paymentDueDate || ''}\" required></td>
<td>
<select class=\"form-control\" name=\"paymentMethod[]\">
<option value=\"0\">Ödeme Yöntemi Seçin</option>
";
// line 2502
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable((isset($context["paymentMethods"]) || array_key_exists("paymentMethods", $context) ? $context["paymentMethods"] : (function () { throw new RuntimeError('Variable "paymentMethods" does not exist.', 2502, $this->source); })()));
foreach ($context['_seq'] as $context["_key"] => $context["paymentMethod"]) {
// line 2503
yield " <option value=\"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["paymentMethod"], "id", [], "any", false, false, false, 2503), "html", null, true);
yield "\" \${val.paymentMethod === '";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["paymentMethod"], "id", [], "any", false, false, false, 2503), "html", null, true);
yield "' ? 'selected' : ''}>
";
// line 2504
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["paymentMethod"], "name", [], "any", false, false, false, 2504), "html", null, true);
yield "
</option>
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['paymentMethod'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 2507
yield " </select>
</td>
<td><input type=\"text\" class=\"form-control\" value=\"\${val.description || ''}\" name=\"description[]\">
<input type=\"hidden\" name=\"voucherCode[]\" value=\"\${val.voucherCode || ''}\">
</td>
<td><button type=\"button\" class=\"btn btn-primary btn-sm save-payment-row\">";
// line 2512
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("save", [], "messages");
yield "</button></td>
<td><button type=\"button\" class=\"btn btn-danger btn-sm delete-payment\">";
// line 2513
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("delete", [], "messages");
yield "</button></td>
</tr>`;
// Yeni satırı tabloya ekle
\$('#payment-list-table-tbody').append(newRow);
\$('#add-payment-modal').modal(\"hide\");
resetPaymentForm();
});
calculateAndWriteDiffBetweenPaymentAndSolds();
}
function updatePaymentByUUID(uuid, updatedData) {
const paymentIndex = payments.findIndex(payment => payment.uuid === uuid);
if (paymentIndex === -1) {
console.error(`Payment with UUID \${uuid} not found!`);
return false;
}
payments[paymentIndex] = {
...payments[paymentIndex], // Mevcut veriler
...updatedData // Yeni veriler
};
fetchPaymentForm();
return true;
}
function calculateAndWriteDiffBetweenPaymentAndSolds(existingTotals = null) {
const totals = existingTotals || aggregateSoldTotals();
const saleTotalRounded = totals.grandTotalRounded;
const paymentTotal = convertToDecimal(calculateTotalPaymentAmount());
const diff = totals.grandTotal.minus(paymentTotal);
const diffRounded = diff.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
\$('#total-amount-span').html(addCommas(saleTotalRounded.toFixed(2)));
\$('#remainder-amount-span').html(addCommas(diffRounded.toFixed(2)));
}
function resetPaymentForm() {
\$('#payment_amount').val('');
\$('#payment_description').val('');
\$('#payment_voucherCode').val('');
\$('#payment_voucherCode').closest('.form-group').hide(); // Reset visibility
}
\$(document).on('click', '.save-payment-row', function() {
const \$row = \$(this).closest('tr');
const uuid = \$row.attr('id');
const updatedData = {
amount: convertToDecimal(\$row.find('input[name=\"paymentAmount[]\"]').val().replace(',', '')).toNumber(),
status: \$row.find('select[name=\"paymentStatus[]\"]').val(),
paymentDate: \$row.find('input[name=\"paymentDate[]\"]').val(),
paymentDueDate: \$row.find('input[name=\"paymentDueDate[]\"]').val(),
paymentMethod: \$row.find('select[name=\"paymentMethod[]\"]').val(),
description: \$row.find('input[name=\"description[]\"]').val(),
voucherCode: \$row.find('input[name=\"voucherCode[]\"]').val()
};
const result = updatePaymentByUUID(uuid, updatedData);
if (result) {
showSuccessAlert(\"Ödeme satırı güncellendi :\", updatedData);
} else {
console.log(\"Failed to update payment.\");
}
});
function resetAddProductForm() {
\$('#products_sold_quantity').val(0);
\$('#products_sold_totalUnitPurchasePrice').val(0);
\$('#products_sold_unAllocatedQuantity').val(0);
\$('#products_sold_totalPuchasePrice').val(0);
}
function calculatePrice() {
var quantityP = convertToFloat(\$('#products_sold_quantity').val());
var unAllocatedQuantityP = convertToFloat(\$('#products_sold_unAllocatedQuantity').val());
var totalUnitPurchasePriceP = convertToFloat(\$('#products_sold_totalUnitPurchasePrice').val());
var taxP = convertToFloat(\$('#products_sold_tax').val());
var discountP = convertToFloat(\$('#products_sold_discount').val());
var totalAmount = (quantityP + unAllocatedQuantityP) * totalUnitPurchasePriceP;
var discountedAmount = totalAmount - (totalAmount * (discountP / 100));
var finalAmount = discountedAmount + (discountedAmount * (taxP / 100));
finalAmount = roundToTwoDecimals(finalAmount);
\$('#products_sold_totalPuchasePrice').val(finalAmount);
}
\$(document).on('change', '#products_sold_tax', calculatePrice);
\$(document).on('change', '#products_sold_discount', calculatePrice);
\$(document).on('change', '#products_sold_quantity', calculatePrice);
\$(document).on('change', '#products_sold_totalUnitPurchasePrice', calculatePrice);
function roundToTwoDecimals(number) {
var factor = Math.pow(10, 2);
return (Math.round(number * factor) / factor).toFixed(2);
}
function validate(s) {
var rgx = /^[0-9]*\\.?[0-9]*\$/;
return s.match(rgx);
}
function convertToFloat(value) {
if (typeof value === \"string\") {
value = value.replace(',', '');
}
return parseFloat(value);
}
\$(document).on('change', 'input[name=\"totalUnitPurchasePrice[]\"]', function() {
var unitPrice = \$(this).val();
});
var productCode = 0;
\$('#productselect').on('change', function() {
\$.ajax({
url: \"/stock-detail/\" + \$('#productselect option:selected').val(),
dataType: 'JSON',
method: 'GET',
success: function(response) {
var productid = response.productid;
var product = products.find(function(item) {
return item.productid === productid;
});
product.length = response.length;
product.priceFob = response.priceFob;
product.priceNavlun = response.priceNavlun;
product.totalQuantity = response.totalQuantity;
product.quantity = response.quantity;
product.unAllocatedQuantity = response.unAllocatedQuantity;
product.thickness = response.thickness;
product.totalUnitPrice = response.totalUnitPrice;
product.width = response.width;
selectProduct(product.productid);
getProductCost(product.productid);
fetchStocks();
productname.val(product.name);
productmeasurement.val(product.measurement);
productCode = product.code;
unitPriceFob.val(addCommas(product.priceFob));
unitPricenavlun.val(addCommas(product.priceNavlun));
totalUnitPrice.val(addCommas(product.priceNavlun + response.priceFob));
totalQuantity = product.totalQuantity;
productId = product.productid;
// Seçilen ürün için birimleri yükle ve ana birimi seç
fillUnitSelects(product);
computeAndShowBaseUnitEquivalent();
},
error: function(error) {
// Hata yönetimi
}
});
});
function isProductService(productId){
const product = products.find(product => product.productid === productId);
const productType = product.productType.key;
if(productType === \"INSTALLATION_SERVICE\" || productType === \"MAINTENANCE_SERVICE\" || productType === \"CONSULTATION_SERVICE\" || productType === 'TREE'){
return true;
}
return false;
}
// TODO
\$('#add-product-row-btn').on('click', function() {
var productId = \$('#productselect').val();
if (!productId || productId <= 0) {
showErrorAlert(\"Ürün seçimi yapın\");
return;
}
let product = getSelectedProduct();
const productType = product.productType.key;
var quantity = \$('#adder-allocated-quantity').val();
var unAllocatedQuantity = \$('#adder-unallocated-quantity').val();
var unitPrice = \$('#adder-price').val();
var tax = \$('#adder-tva-select').val();
var discount = \$('#adder-discount-select').val();
var totalPurchasePrice = \$('#adder-total').val();
quantity = convertToDecimal(quantity);
unAllocatedQuantity = convertToDecimal(unAllocatedQuantity);
unitPrice = convertToDecimal(unitPrice);
tax = convertToDecimal(tax);
discount = convertToDecimal(discount);
totalPurchasePrice = convertToDecimal(totalPurchasePrice);
if(productType === 'TREE') {
}
checkStockQuantity(quantity);
const selectedUnitId = \$('#adder-quantity-unit-select').val();
if (!selectedUnitId || String(selectedUnitId) === '') {
showErrorAlert('Lütfen birim seçiniz.');
return;
}
const item = addProductToSoldList(
product.productid,
product.name,
product.code,
product.measurement,
quantity,
unitPrice,
tax,
discount,
totalPurchasePrice,
product.warehouseid,
unAllocatedQuantity,
product.thickness,
product.width,
product.length,
product.cost,
product.productType,
selectedUnitId,
);
if(isProductService(product.productid)){
\$('#update_service_id').val(item.uuid);
\$('#updateServiceModal').modal('show');
}else{
if(quantity.toNumber() === 0 && unAllocatedQuantity.toNumber() === 0) {
showErrorAlert(\"Miktar veya tahsis edilmemiş miktar 0 olamaz\");
return;
}
}
fetchSoldListRows();
resetAdderRow();
});
\$('#service_modal_save_changes_btn').on('click', function() {
const uuid = \$('#update_service_id').val();
const soldProduct = soldList.find(soldProduct => soldProduct.uuid === uuid);
const product = products.find(product => product.productid === soldProduct.productid);
const productType = product.productType.key;
// Modal değerlerini geri alıp objeye yazıyoruz
soldProduct.productName = \$('#update_service_name').val();
soldProduct.cost = parseFloat(\$('#update_service_cost').val());
soldProduct.description = \$('#update_service_description').val();
const tr = \$('#' + uuid);
console.log(\"Servis güncellendi:\", soldProduct);
\$('#updateServiceModal').modal('hide');
// Rows'ları yeniden render et ki service adı dropdown'da görünsün
fetchSoldListRows();
updateTotalAmounts();
if(isProductService(product.productid)){
addServiceEditButtonIfNeeded(uuid);
}
});
\$(document).on('click', '.edit-service-button', function () {
const uuid = \$(this).data('uuid');
const soldProduct = soldList.find(sp => sp.uuid === uuid);
\$('#update_service_id').val(uuid);
\$('#update_service_name').val(soldProduct?.productName || \"\");
\$('#update_service_cost').val(soldProduct?.cost || \"0.00\");
\$('#update_service_description').val(soldProduct?.description || \"\");
\$('#updateServiceModal').modal('show');
});
// TODO:
function addServiceEditButtonIfNeeded(uuid) {
const soldProduct = soldList.find(sp => sp.uuid === uuid);
const product = products.find(p => p.productid === soldProduct.productid);
const productType = product.productType.key;
// Sadece ürün service ise buton ekle
if (product && isProductService(product.productid)) {
const tr = \$('#' + uuid);
// Eğer daha önce eklenmediyse ekle
if (tr.find('.edit-service-button').length === 0) {
const btn = `
<button type=\"button\"
class=\"btn btn-warning btn-sm edit-service-button\"
data-uuid=\"\${uuid}\"
title=\"Servisi Güncelle\">
<i class=\"fas fa-edit\"></i>
</button>`;
// örnek: sil butonunun yanına ekleyelim
tr.find('.btn-group').prepend(btn);
}
}
}
// Reset adder row form after adding product
function resetAdderRow() {
\$('#productselect').val('').trigger('change');
\$('#adder-allocated-quantity').val('0');
\$('#adder-unallocated-quantity').val('0');
\$('#adder-price').val('0');
\$('#adder-discount-select').val('0');
\$('#adder-tva-select').val('0');
\$('#adder-total').val('0');
\$('#unalloc-display').text('0');
clearUnitSelects();
\$('#info-details-content').hide();
\$('#show-info-btn i').removeClass('fa-chevron-up').addClass('fa-info-circle');
}
// =============================================================================
// EVENT HANDLERS FOR SALES LIST EDITING
// =============================================================================
// Show/hide product info details
\$(document).on('click', '.show-row-info-btn', function() {
const \$infoContent = \$(this).closest('tr').next('.info-details-row-class').find('.info-details-content-class');
const \$icon = \$(this).find('i');
if (\$infoContent.is(':visible')) {
\$infoContent.hide();
\$icon.removeClass('fa-chevron-up').addClass('fa-info-circle');
\$(this).closest('tr').next('.info-details-row-class').hide();
} else {
\$infoContent.show();
\$icon.removeClass('fa-info-circle').addClass('fa-chevron-up');
\$(this).closest('tr').next('.info-details-row-class').show();
}
});
\$(document).on('change', '.product-select-row', function() {
const uuid = \$(this).data('uuid');
const newProductId = parseInt(\$(this).val());
const soldItemIndex = soldList.findIndex(item => item.uuid === uuid);
if (soldItemIndex > -1) {
const newProduct = products.find(p => p.productid === newProductId);
if (newProduct) {
soldList[soldItemIndex].productid = newProduct.productid;
soldList[soldItemIndex].productName = newProduct.name;
soldList[soldItemIndex].code = newProduct.code;
soldList[soldItemIndex].measurement = newProduct.measurement;
soldList[soldItemIndex].cost = newProduct.cost;
}
fetchSoldListRows();
updateTotalAmounts();
}
console.table(soldList);
});
// Handle unit price changes in sold list rows
\$(document).on('input change', '.price-input-row', function() {
const uuid = \$(this).data('uuid');
const value = \$(this).val();
// Birim fiyat manuel değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
updateSoldListItem(uuid, 'unitPrice', value);
});
// Handle discount changes in sold list rows
\$(document).on('change', '.discount-select-row', function() {
const uuid = \$(this).data('uuid');
const value = \$(this).val();
// İndirim değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
updateSoldListItem(uuid, 'discount', value);
});
// Handle tax changes in sold list rows
\$(document).on('change', '.tax-select-row', function() {
const uuid = \$(this).data('uuid');
const value = \$(this).val();
// KDV değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
updateSoldListItem(uuid, 'tax', value);
});
// Handle unit selection changes in sold list rows
\$(document).on('change', '.quantity-unit-select', function() {
const uuid = \$(this).data('uuid');
const unitId = \$(this).val();
const \$quantityInput = \$(`.quantity-input-row[data-uuid=\"\${uuid}\"]`);
const currentQuantity = parseFloat(\$quantityInput.val()) || 0;
// Update the selected unit in sold list
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex !== -1) {
const item = soldList[itemIndex];
const oldUnitId = item.selectedUnitId;
item.selectedUnitId = unitId;
// Show unit conversion equivalent
showUnitConversionForRow(uuid, item.productid, currentQuantity, unitId);
// If changing from one unit to another, convert the quantity
if (oldUnitId && oldUnitId !== unitId && unitId && currentQuantity > 0) {
convertQuantityBetweenUnits(item.productid, currentQuantity, oldUnitId, unitId, function(convertedQuantity) {
if (convertedQuantity !== null) {
\$quantityInput.val(convertedQuantity.toFixed(2));
updateSoldListItem(uuid, 'quantity', convertedQuantity);
// Update conversion display with new quantity
showUnitConversionForRow(uuid, item.productid, convertedQuantity, unitId);
}
});
}
// Recompute and store base unit quantity for the row
computeBaseUnitQuantity(item.productid, unitId, parseFloat(\$quantityInput.val()) || 0, function(baseQty){
const idx = soldList.findIndex(sp => sp.uuid === uuid);
if(idx !== -1){ soldList[idx].baseUnitQuantity = convertToDecimal(baseQty || 0).toNumber(); }
});
}
});
// Handle quantity changes in sold list rows to update conversion display
\$(document).on('input change', '.quantity-input-row', function() {
const uuid = \$(this).data('uuid');
const value = \$(this).val();
const item = soldList.find(item => item.uuid === uuid);
if (item && item.selectedUnitId) {
showUnitConversionForRow(uuid, item.productid, parseFloat(value) || 0, item.selectedUnitId);
}
// Miktar manuel değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
updateSoldListItem(uuid, 'quantity', value);
// Also refresh baseUnitQuantity on quantity change
if (item) {
computeBaseUnitQuantity(item.productid, item.selectedUnitId, parseFloat(value) || 0, function(baseQty){
const idx = soldList.findIndex(sp => sp.uuid === uuid);
if(idx !== -1){ soldList[idx].baseUnitQuantity = convertToDecimal(baseQty || 0).toNumber(); }
});
}
});
// Show unit conversion equivalent for a specific row
function showUnitConversionForRow(uuid, productId, quantity, fromUnitId) {
if (!fromUnitId || !quantity || quantity <= 0) {
updateRowQuantityHint(uuid, '');
return;
}
const product = products.find(p => p.productid === productId);
if (!product || !product.measurementUnitId) {
updateRowQuantityHint(uuid, '');
return;
}
// If already in product's main unit, no conversion needed
if (fromUnitId == product.measurementUnitId) {
updateRowQuantityHint(uuid, '');
return;
}
\$.ajax({
url: '/admin/measurement/convert-to-product-unit',
method: 'POST',
dataType: 'json',
data: {
productId: productId,
fromUnitId: fromUnitId,
quantity: quantity
},
success: function(resp) {
if (resp && resp.success) {
const unitText = product.measurementUnit ? (' ' + product.measurementUnit) : '';
updateRowQuantityHint(uuid, (Number(resp.result).toFixed(2)) + unitText);
} else {
updateRowQuantityHint(uuid, '');
}
},
error: function() {
updateRowQuantityHint(uuid, '');
}
});
}
// Update quantity hint for a specific row
function updateRowQuantityHint(uuid, text) {
let \$hint = \$(`.quantity-conversion-hint[data-uuid=\"\${uuid}\"]`);
if (\$hint.length === 0) {
const \$quantityCell = \$(`.quantity-input-row[data-uuid=\"\${uuid}\"]`).closest('td');
\$quantityCell.append(`<small class=\"form-text text-muted quantity-conversion-hint\" data-uuid=\"\${uuid}\"></small>`);
\$hint = \$(`.quantity-conversion-hint[data-uuid=\"\${uuid}\"]`);
}
\$hint.text(text || '');
}
// Convert quantity between different measurement units
function convertQuantityBetweenUnits(productId, quantity, fromUnitId, toUnitId, callback) {
if (!fromUnitId || !toUnitId || fromUnitId === toUnitId) {
callback(quantity);
return;
}
\$.ajax({
url: '/admin/measurement/convert-between-units',
method: 'POST',
dataType: 'json',
data: {
productId: productId,
fromUnitId: fromUnitId,
toUnitId: toUnitId,
quantity: quantity
},
success: function(resp) {
if (resp && resp.success) {
callback(parseFloat(resp.result));
} else {
console.error('Unit conversion failed:', resp);
callback(null);
}
},
error: function() {
console.error('Unit conversion request failed');
callback(null);
}
});
}
// Handle total price changes (reverse calculation to unit price)
\$(document).on('change blur', '.total-input-row', function() {
updateUnitPriceFromTotal(\$(this).data('uuid'));
});
// Handle delete row button clicks
const currencySymbol = \"";
// line 3057
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\TranslationExtension']->trans("currency.symbol"), "js"), "html", null, true);
yield "\";
const PAYMENT_TOLERANCE = 0.01;
const AUTO_PAYMENT_DESCRIPTION = 'Sistem tarafından ödeme otomatik oluşturuldu';
const AUTO_PAYMENT_STATUS_TEXT = 'Ödendi';
let autoPaymentModalState = null;
let cachedCashPaymentMethod = null;
function detectCashPaymentMethod() {
const \$options = \$('#payment_paymentMethod option');
let fallback = null;
let cashCandidate = null;
\$options.each(function() {
const \$option = \$(this);
const value = (\$option.val() || '').trim();
if (!value) {
return;
}
if (!fallback) {
fallback = { id: value, label: (\$option.text() || '').trim() };
}
const label = (\$option.text() || '').toLowerCase();
if (label.includes('cash') || label.includes('nakit')) {
cashCandidate = { id: value, label: (\$option.text() || '').trim() };
return false;
}
});
if (cashCandidate) {
return cashCandidate;
}
if (fallback) {
return fallback;
}
return { id: '1', label: 'Cash' };
}
function ensureCashPaymentMethod() {
if (!cachedCashPaymentMethod) {
cachedCashPaymentMethod = detectCashPaymentMethod();
}
return cachedCashPaymentMethod;
}
function normalizeCurrency(value) {
return convertToDecimal(value).toDecimalPlaces(2, Decimal.ROUND_HALF_UP).toNumber();
}
function formatCurrency(value) {
const normalized = normalizeCurrency(value);
const formatted = addCommas(normalized.toFixed(2));
return formatted.replace(/,/g, '#').replace(/\\./g, ',').replace(/#/g, '.') + ' ' + currencySymbol;
}
function calculateTotalPaymentAmount() {
let total = new Decimal(0);
payments.forEach(function(payment) {
total = total.plus(convertToDecimal(payment.amount));
});
return total.toNumber();
}
function getSaleDateValue() {
const saleDate = \$('#sales_form_salesDate').val();
if (saleDate && saleDate.trim() !== '') {
return saleDate;
}
return new Date().toISOString().split('T')[0];
}
function createAutoPaymentPayload(amount, saleDate) {
const method = ensureCashPaymentMethod();
return {
uuid: crypto.randomUUID(),
amount: normalizeCurrency(amount),
status: '0',
paymentDate: saleDate,
paymentDueDate: saleDate,
paymentMethod: method.id,
description: AUTO_PAYMENT_DESCRIPTION
};
}
function openAutoPaymentModal(config) {
autoPaymentModalState = {
scenario: config.scenario,
amount: normalizeCurrency(config.amount),
saleDate: config.saleDate,
saleTotal: normalizeCurrency(config.saleTotal),
currentPayments: normalizeCurrency(config.currentPayments)
};
const method = ensureCashPaymentMethod();
let message = '';
if (config.scenario === 'no-payment') {
message = 'Bu satış için henüz ödeme girilmedi. Aşağıdaki bilgilerle otomatik bir nakit ödeme oluşturulacaktır.';
} else {
message = 'Girilen ödemeler satış toplamı ile uyumsuz. Eksik tutar otomatik bir nakit ödeme olarak eklenecektir.';
}
\$('#auto-payment-message').text(message);
\$('#auto-payment-sale-total').text(formatCurrency(autoPaymentModalState.saleTotal));
\$('#auto-payment-current-payments').text(formatCurrency(autoPaymentModalState.currentPayments));
\$('#auto-payment-amount').text(formatCurrency(autoPaymentModalState.amount));
\$('#auto-payment-method').text(method.label);
\$('#auto-payment-status').text(AUTO_PAYMENT_STATUS_TEXT);
\$('#auto-payment-description').text(AUTO_PAYMENT_DESCRIPTION);
\$('#confirm-auto-payment').prop('disabled', false);
\$('#autoPaymentModal').modal('show');
}
function validateSalesForm() {
const form = \$('form[name=\"sales_form\"]');
if (!form || form.length === 0) {
return true;
}
return form[0].reportValidity();
}
function submitSalesRequest() {
if (!validateSalesForm()) {
return;
}
const salesData = {
payments: payments,
soldList: soldList,
invoice: {
invoiceNumber: \$('#sales_form_invoiceNumber').val(),
customer: \$('#sales_form_customer').val(),
paymentStatus: \$('#sales_form_paymentStatus').val(),
salesDate: \$('#sales_form_salesDate').val(),
deliveryDate: \$('#sales_form_deliveryDate').val(),
dueDate: \$('#sales_form_dueDate').val(),
validDate: \$('#sales_form_validDate').val(),
seller: \$('#sales_form_seller').val(),
description: \$('#sales_form_description').val(),
shippingNotes: \$('#sales_form_shippingNotes').val(),
paymentNotes: \$('#sales_form_paymentNotes').val(),
salesType: \$('input[name=\"salesType\"]').filter(':checked').val() || 'proforma',
salesStatus: \$('#sales_form_status').val() ? \$('#sales_form_status').val() : '0'
}
};
\$.ajax({
url: \"";
// line 3203
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("app_admin_sales_create");
yield "\",
method: 'POST',
data: salesData,
success: function(response) {
window.location.href = \"/sales/detail/\" + response.id;
Swal.fire({
icon: 'success',
title: 'Başarılı',
text: 'Satış başarıyla oluşturuldu'
});
},
error: function(xhr) {
var response = JSON.parse(xhr.responseText);
showErrorAlert(response.message);
}
});
}
function finalizeSalesSubmission() {
const saleTotal = convertToDecimal(getTotalSoldAmount());
const totalPayments = convertToDecimal(calculateTotalPaymentAmount());
const difference = saleTotal.minus(totalPayments).abs().toNumber();
if (difference > PAYMENT_TOLERANCE) {
showErrorAlert('Ödemeler satış toplamı ile hâlâ uyumsuz. Lütfen kontrol edin.');
return;
}
submitSalesRequest();
}
\$('#confirm-auto-payment').on('click', function() {
if (!autoPaymentModalState) {
return;
}
const saleDate = autoPaymentModalState.saleDate || getSaleDateValue();
const autoPayment = createAutoPaymentPayload(autoPaymentModalState.amount, saleDate);
payments.push(autoPayment);
fetchPaymentForm();
calculateAndWriteDiffBetweenPaymentAndSolds();
\$('#confirm-auto-payment').prop('disabled', true);
\$('#autoPaymentModal').modal('hide');
autoPaymentModalState = null;
finalizeSalesSubmission();
});
\$('#autoPaymentModal').on('hidden.bs.modal', function() {
\$('#confirm-auto-payment').prop('disabled', false);
autoPaymentModalState = null;
});
ensureCashPaymentMethod();
\$(document).on('click', '.delete-row-button', function() {
const uuid = \$(this).data('uuid');
deleteProductInSoldList(uuid);
\$(`tr[id=\"\${uuid}\"]`).remove();
\$(`.info-details-row-class[data-uuid=\"\${uuid}\"]`).remove();
fetchStocks();
updateTotalAmounts();
fetchPaymentForm();
\$('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
});
// Handle unallocated quantity modal for rows
\$(document).on('click', '.open-unallocated-modal-row', function() {
const uuid = \$(this).data('uuid');
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex === -1) return;
// Değeri gizli input yerine doğrudan soldList'ten oku
const currentQty = soldList[itemIndex].unAllocatedQuantity || 0;
\$('#modal-unallocated-quantity').val(Number(currentQty).toFixed(2));
// Store current uuid for saving later
\$('#unallocatedModal').data('current-uuid', uuid);
\$('#unallocatedModal').modal('show');
});
// Save unallocated quantity from modal for rows
\$(document).on('click', '#save-unallocated-btn', function() {
const uuid = \$('#unallocatedModal').data('current-uuid');
// Sadece satırlar için olan bölüm (uuid varsa)
if (uuid) {
const qty = \$('#modal-unallocated-quantity').val() || 0;
const newUnallocatedQty = convertToDecimal(qty).toNumber();
// 1. soldList'teki ilgili ürünü bul ve unAllocatedQuantity'yi güncelle
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex > -1) {
soldList[itemIndex].unAllocatedQuantity = newUnallocatedQty;
}
// 2. DOĞRU SATIRI HEDEFLEYEREK butondaki sayıyı güncelle
const \$button = \$(`.open-unallocated-modal-row[data-uuid=\"\${uuid}\"]`);
\$button.find('.unalloc-display').text(newUnallocatedQty.toFixed(2));
// 3. Tahsis edilmemiş miktar değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
// 4. Satırın genel toplamlarını ve stokları güncelle
updateSoldProductFromRow(uuid);
\$('#unallocatedModal').modal('hide');
} else { // Bu kısım adder-row için, dokunmuyoruz
const qty = \$('#modal-unallocated-quantity').val() || 0;
\$('#adder-unallocated-quantity').val(qty).trigger('change');
\$('#unalloc-display').text(qty);
onInputChange.call(\$('#adder-unallocated-quantity')[0]);
\$('#unallocatedModal').modal('hide');
}
});
// Flag to prevent circular updates between unit price and total
let isUpdatingFromTotal = false;
let isUpdatingFromUnitPrice = false;
// adder-row input alanlarının değişikliklerini dinleme
\$('#adder-row').on('input change',
'#adder-allocated-quantity, ' +
'#adder-unallocated-quantity, ' +
'#adder-price, ' +
'#adder-discount-select, ' +
'#adder-tva-select, ' +
'#adder-total', onInputChange);
// Birim dönüşümünü yazma bittiğinde (blur) ve birim seçiminde (change) yap
\$('#adder-row').on('blur', '#adder-allocated-quantity', function(){
computeAndShowBaseUnitEquivalent();
});
\$('#adder-row').on('change', '#adder-quantity-unit-select', function(){
computeAndShowBaseUnitEquivalent();
});
// Adder rowdaki input alanlarının değişikliklerini dinleme
function onInputChange() {
if(\$(this).attr('id') === 'adder-total') {
// Toplam manuel değiştirildi → birim fiyatı hesapla, ama toplamı tekrar değiştirme
if(isUpdatingFromUnitPrice) return;
isUpdatingFromTotal = true;
const totalAmount = convertToDecimal(\$('#adder-total').val() || 0).toNumber();
const allocatedQuantity = convertToDecimal(\$('#adder-allocated-quantity').val() || 0).toNumber();
const unallocatedQuantity = convertToDecimal(\$('#adder-unallocated-quantity').val() || 0).toNumber();
const discountPercentage = convertToDecimal(((\$('#adder-discount-select').val() || '0') + '').replace('%', '')).toNumber();
const tvaPercentage = convertToDecimal(((\$('#adder-tva-select').val() || '0') + '').replace('%', '')).toNumber();
const unitPrice = calculateUnitPrice(totalAmount, allocatedQuantity, unallocatedQuantity, tvaPercentage, discountPercentage);
\$('#adder-price').val(convertToDecimal(unitPrice).toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
setTimeout(function(){ isUpdatingFromTotal = false; }, 0);
} else {
// Birim fiyat, miktar, indirim veya KDV değişti → toplamı hesapla
if(isUpdatingFromTotal) return;
isUpdatingFromUnitPrice = true;
const allocatedQuantity = convertToDecimal(\$('#adder-allocated-quantity').val() || 0).toNumber();
const unallocatedQuantity = convertToDecimal(\$('#adder-unallocated-quantity').val() || 0).toNumber();
const price = convertToDecimal(\$('#adder-price').val() || 0).toNumber();
const discountPercentage = convertToDecimal(((\$('#adder-discount-select').val() || '0') + '').replace('%', '')).toNumber();
const tvaPercentage = convertToDecimal(((\$('#adder-tva-select').val() || '0') + '').replace('%', '')).toNumber();
calculateTotal(allocatedQuantity, unallocatedQuantity, price, tvaPercentage, discountPercentage);
setTimeout(function(){ isUpdatingFromUnitPrice = false; }, 0);
}
}
function roundToNearestCent(amount) {
return Math.round(amount * 100) / 100;
}
// Toplam tutarı hesaplama
// Her ara adımda virgülden sonra 2 hane gösterilecek, 3. hane 5 ve üzeri ise yukarı yuvarlanacak
function calculateTotal(allocatedQuantity, unallocatedQuantity, price, tvaPercentage, discountPercentage) {
const totals = calculateRowFinancials({
quantity: allocatedQuantity,
unAllocatedQuantity: unallocatedQuantity,
unitPrice: price,
discount: discountPercentage,
tax: tvaPercentage
});
\$('#adder-total').val(totals.totalRounded.toFixed(2));
}
// Toplam tutardan Birim fiyatı hesaplama
function calculateUnitPrice(totalAmount, allocatedQuantity, unallocatedQuantity, tvaPercentage, discountPercentage) {
const totalQuantity = convertToDecimal(allocatedQuantity || 0).plus(convertToDecimal(unallocatedQuantity || 0));
if (totalQuantity.isZero()) {
return 0;
}
const total = convertToDecimal(totalAmount || 0);
// Eğer toplam 0 ise, birim fiyat da 0 olmalı
if (total.isZero()) {
return 0;
}
const discountMultiplier = new Decimal(1).minus(convertToDecimal(discountPercentage || 0).div(100));
const taxMultiplier = new Decimal(1).plus(convertToDecimal(tvaPercentage || 0).div(100));
// %100 indirimde discountMultiplier = 0 olur, bu durumda birim fiyat hesaplanamaz
// Çünkü total = (unitPrice * quantity * (1 - discount) * (1 + tax))
// discount = 100% ise, unitPrice = 0 olmalı (ya da sonsuz, ama mantıklı olan 0)
if (discountMultiplier.isZero()) {
return 0;
}
if (taxMultiplier.isZero()) {
return 0;
}
// Formül: unitPrice = total / (quantity * (1 - discount%) * (1 + tax%))
const subtotal = total.div(taxMultiplier).div(discountMultiplier);
const unitPrice = subtotal.div(totalQuantity);
return unitPrice.toDecimalPlaces(6, MONEY_ROUNDING_MODE).toNumber();
}
function getProductCost(pid) {
// Popup için URL burada oluşturuluyor.
const url = '/product-cost/' + pid + '/' + getSelectedProduct().warehouseid;
\$.ajax({
url: \"/product-cost-detail/\" + pid + '?warehouse=' + getSelectedProduct().warehouseid,
method: \"POST\",
dataType: \"JSON\",
success: function(data) {
var product = getSelectedProduct();
product.cost = data.cost;
product.measurementUnit = data.measurement;
\$('.stock-info-span').html(data.stock.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" \" + data.measurement);
\$('.cost-info-span').html(data.cost.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" €\"); // Eski detay butonu kaldırıldı
\$('.measurement-unit-info-span').html(data.measurement);
\$('#hidden-cost-input').val(data.cost);
// \"Maliyet\" popup açıyor maliyet detaylarını gösteriyor.
const detailsHtml = `
<div class=\"row align-items-center\">
<div class=\"col-md-3\">\${product.name}</div>
<div class=\"col-md-3\">
<strong>
<button type=\"button\" class=\"btn btn-link btn-sm p-0 cost-popup-btn\" data-url=\"\${url}\">Maliyet: \${data.cost.toFixed(2)} €</button>
</strong>
</div>
<div class=\"col-md-3\"><strong>Stok:</strong> \${data.stock.toFixed(2)} \${data.measurement}</div>
<div class=\"col-md-3\"><strong>Birim:</strong> \${data.measurement}</div>
</div>
`;
\$('#info-details-content').html(detailsHtml);
return data;
},
error: function(data) {
\$('#info-details-content').html('<div class=\"text-danger\">Ürün maliyet bilgileri yüklenemedi.</div>');
}
});
}
\$(document).on('click', '.cost-popup-btn', function() {
const url = \$(this).data('url');
if (url) {
createPopup(url, 'cost-detail-window');
}
});
// Unallocated modal açıldığında mask'i uygula
\$('#unallocatedModal').on('shown.bs.modal', function() {
applyMasks();
});
// Unallocated modal aç/kapat ve değerleri senkronize et
\$(document).on('click', '#open-unallocated-modal', function() {
// Mevcut değerleri moda aktarma
const currentQty = \$('#adder-unallocated-quantity').val() || 0;
const currentUnit = \$('#adder-unallocated-quantity-unit-select').val() || '';
\$('#modal-unallocated-quantity').val(Number(currentQty).toFixed(2));
// Seçili ürünün saklanan birimlerini modal select'ine doldur
const selectedProduct = getSelectedProduct();
if (selectedProduct && selectedProduct.convertibleUnits) {
populateSelectWithUnits(\$('#modal-unallocated-unit'), selectedProduct.convertibleUnits);
} else {
\$('#modal-unallocated-unit').empty().append('<option value=\"\">-</option>');
}
\$('#modal-unallocated-unit').val(currentUnit);
\$('#unallocatedModal').modal('show');
});
// 'Tahsis Edilmemiş Miktar' Modal'ındaki Kaydet Butonunun Düzeltilmiş Hali
\$(document).on('click', '#save-unallocated-btn', function() {
const uuid = \$('#unallocatedModal').data('current-uuid');
const qty = \$('#modal-unallocated-quantity').val() || 0;
const newUnallocatedQty = convertToDecimal(qty).toNumber();
// DURUM 1: Satış listesindeki bir satır güncelleniyor (uuid mevcut)
if (uuid) {
// 1. soldList'teki ilgili ürünü bul ve unAllocatedQuantity'yi güncelle
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex > -1) {
soldList[itemIndex].unAllocatedQuantity = newUnallocatedQty;
}
// 2. Sadece ilgili satırı ID ile hedefle ve içindeki göstergeyi güncelle
const \$row = \$(`#\${uuid}`);
\$row.find('.unalloc-display').text(newUnallocatedQty.toFixed(2));
// 3. Satırın genel toplamlarını ve stokları güncelle
updateSoldProductFromRow(uuid);
}
// DURUM 2: Adder-row (ekleme satırı) güncelleniyor (uuid mevcut değil)
else {
// Sadece adder-row'daki gizli input'u ve göstergeyi güncelle
\$('#adder-unallocated-quantity').val(newUnallocatedQty).trigger('change');
\$('#unalloc-display').text(newUnallocatedQty.toFixed(2));
// Adder-row'daki toplamı yeniden hesapla
onInputChange.call(\$('#adder-unallocated-quantity')[0]);
}
// İşlem tamamlandıktan sonra modalı kapat
\$('#unallocatedModal').modal('hide');
// Modal'daki uuid verisini temizle ki bir sonraki açılışta karışmasın
\$('#unallocatedModal').removeData('current-uuid');
});
function formatState(state) {
if (!state.id) {
return state.text;
}
var baseUrl = \"/images\";
var \$state = \$(
'<span><img src=\"' + baseUrl + '/' + state.element.dataset.image + '\" class=\"img-flag list-image\" style=\"width: 50px;\"/> ' + state.text + '</span>'
);
return \$state;
};
\$('#productselect').select2({
theme: 'bootstrap4',
dropdownCssClass: 'custom-select-dropdown',
placeholder: 'Ürün Seçin',
allowClear: true
});
function showErrorAlert(message) {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: '' + message,
background: 'white',
});
}
window.addEventListener(\"error\", function(event) {
showErrorAlert(event.message);
});
function showSuccessAlert(message) {
Swal.fire({
icon: 'success',
title: 'Başarılı',
text: '' + message,
background: 'white',
});
}
\$('#submitbtn').on('click', function(event) {
event.preventDefault();
if (soldList.length === 0) {
showErrorAlert(\"";
// line 3581
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("sale.errors.cannot_save_without_products", [], "messages");
yield "\");
return;
}
if (!validateSalesForm()) {
return;
}
const saleTotalValue = convertToDecimal(getTotalSoldAmount()).toNumber();
const currentPaymentTotal = convertToDecimal(calculateTotalPaymentAmount()).toNumber();
const saleDate = getSaleDateValue();
if (payments.length === 0 && saleTotalValue > PAYMENT_TOLERANCE) {
openAutoPaymentModal({
scenario: 'no-payment',
amount: saleTotalValue,
saleTotal: saleTotalValue,
currentPayments: currentPaymentTotal,
saleDate: saleDate
});
return;
}
const diffValue = convertToDecimal(saleTotalValue).minus(convertToDecimal(currentPaymentTotal)).toNumber();
if (Math.abs(diffValue) > PAYMENT_TOLERANCE) {
if (diffValue < 0) {
showErrorAlert('Girilen ödeme tutarı satış tutarını aşıyor. Lütfen ödeme kayıtlarını kontrol edin.');
return;
}
openAutoPaymentModal({
scenario: 'mismatch',
amount: diffValue,
saleTotal: saleTotalValue,
currentPayments: currentPaymentTotal,
saleDate: saleDate
});
return;
}
submitSalesRequest();
});
function addCommas(number) {
let decimals = 2;
number = (number + '').replace(/[^0-9+\\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = ',';
var dec = '.';
var s = '';
var toFixedFix = function(n, prec) {
var k = Math.pow(10, prec);
return '' + (Math.round(n * k) / k).toFixed(prec);
};
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (s[0].length > 3) {
s[0] = s[0].replace(/\\B(?=(?:\\d{3})+(?!\\d))/g, sep);
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
\$(document).on('input change', '#products_sold_totalUnitPurchasePrice, #products_sold_quantity, #products_sold_unAllocatedQuantity, #products_sold_discount, #products_sold_tax', function() {
var purchasePrice = \$('#products_sold_totalUnitPurchasePrice').val() || \"0\";
var purchasePriceClean = parseFloat(purchasePrice.replace(/,/g, \"\")) || 0;
var cost = parseFloat(\$('#hidden-cost-input').val()) || 0;
var quantity = \$('#products_sold_quantity').val() || \"0\";
var quantityClean = parseFloat(quantity.replace(/,/g, \"\")) || 0;
var unAllocatedQuantity = \$('#products_sold_unAllocatedQuantity').val() || \"0\";
var unAllocatedQuantityClean = parseFloat(unAllocatedQuantity.replace(/,/g, \"\")) || 0;
var discountPercentage = \$('#products_sold_discount').val() || \"0\";
var discountPercentageClean = parseFloat(discountPercentage.replace(/,/g, \"\")) || 0;
var taxPercentage = \$('#products_sold_tax').val() || \"0\";
var taxPercentageClean = parseFloat(taxPercentage.replace(/,/g, \"\")) || 0;
var totalQuantity = quantityClean + unAllocatedQuantityClean;
var totalSalePriceBeforeDiscount = purchasePriceClean * totalQuantity;
var discountAmount = (totalSalePriceBeforeDiscount * discountPercentageClean) / 100;
var totalSalePriceAfterDiscount = totalSalePriceBeforeDiscount - discountAmount;
var taxAmount = (totalSalePriceAfterDiscount * taxPercentageClean) / 100;
var totalSalePrice = totalSalePriceAfterDiscount + taxAmount;
\$('#products_sold_totalSalePrice').val(addCommas(totalSalePrice.toFixed(2)));
\$('#products_sold_totalPuchasePrice').val(addCommas(totalSalePrice.toFixed(2)));
cost = roundToDecimal(cost);
var salePriceWithoutTax = totalSalePrice / (1 + taxPercentageClean / 100);
var totalCost = totalQuantity * cost;
var profit = salePriceWithoutTax - totalCost;
var profitSpan;
if (profit > 0) {
profitSpan = \"<span style='color:green'>\" + profit.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" €</span>\";
} else {
profitSpan = \"<span style='color:red'>\" + profit.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" €</span>\";
}
\$('.profit-info-span').html(profitSpan);
});
\$('#payment_amount').mask(\"#,##0.00\", {
reverse: true
});
// Show/Hide Voucher Code field based on Payment Method
\$('#payment_paymentMethod').on('change', function() {
var selectedText = \$(this).find(\"option:selected\").text().trim().toLowerCase();
var \$voucherField = \$('#payment_voucherCode');
var \$voucherLabel = \$('label[for=\"payment_voucherCode\"]');
if (selectedText.includes('hediye') || selectedText.includes('gift') || selectedText.includes('çek')) {
\$voucherField.show();
\$voucherLabel.show();
\$voucherField.attr('required', 'required');
} else {
\$voucherField.hide();
\$voucherLabel.hide();
\$voucherField.val('');
\$voucherField.removeAttr('required');
}
});
\$('#sales_form_customer').select2({
theme: 'bootstrap4',
dropdownCssClass: 'custom-select-dropdown',
placeholder: 'Müşteri Seçin',
allowClear: true
});
\$('.js-edit-customer').on('click', function() {
let customerId = \$('#sales_form_customer').val();
if (!customerId) {
showErrorAlert(\"";
// line 3716
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("selectCustomerForEdit", [], "messages");
yield "\");
} else {
\$.ajax({
url: '/admin/customer/detail/' + customerId,
type: 'GET',
dataType: 'json',
success: function(response) {
showUpdateModal(customerId, response);
},
error: function(jqXHR, textStatus, errorThrown) {
if (jqXHR.status === 404) {
showErrorAlert(\"Seçilen müşteri veritabanında bulunamadı.\");
} else {
showErrorAlert('Müşteri bilgileri yüklenirken bir hata oluştu.');
}
clearCustomerFields();
}
});
}
});
\$('#sales_form_customer').on('change', function() {
let customerId = \$(this).val();
if (!customerId) {
clearCustomerFields();
return;
}
\$.ajax({
url: '/admin/customer/detail/' + customerId,
type: 'GET',
dataType: 'json',
success: function(response) {
if (!response.phone || !response.email || !response.address) {
showUpdateModal(customerId, response);
} else {
populateCustomerFields(response);
}
},
error: function(jqXHR, textStatus, errorThrown) {
if (jqXHR.status === 404) {
showErrorAlert(\"Seçilen müşteri veritabanında bulunamadı.\");
} else {
showErrorAlert('Müşteri bilgileri yüklenirken bir hata oluştu.');
}
clearCustomerFields();
}
});
});
function populateCustomerFields(customer) {
\$('#update_customer_fullName').val(customer.fullName);
\$('#update_customer_email').val(customer.email);
\$('#update_customer_phone').val(customer.phone);
\$('#update_customer_address').val(customer.address);
}
function clearCustomerFields() {
\$('#update_customer_fullName, #update_customer_email, #update_customer_phone, #update_customer_address').val('');
}
function showUpdateModal(id, customer) {
\$('#update_customer_id').val(id);
\$('#update_customer_email').val(customer.email || '');
\$('#update_customer_phone').val(customer.phone || '');
\$('#update_customer_address').val(customer.address || '');
\$('#update_customer_fullName').val(customer.fullName || '');
\$('#updateCustomerModal').modal('show');
}
\$('#updateCustomerForm').on('submit', function(e) {
e.preventDefault();
let customerId = \$('#update_customer_id').val();
let formData = {
email: \$('#update_customer_email').val(),
phone: \$('#update_customer_phone').val(),
address: \$('#update_customer_address').val(),
fullName: \$('#update_customer_fullName').val()
};
\$.ajax({
url: '/admin/customer/update/' + customerId,
type: 'POST',
data: formData,
success: function(response) {
\$('#updateCustomerModal').modal('hide');
populateCustomerFields(response);
showSuccessAlert(\"";
// line 3802
echo $this->env->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->trans("customerUpdateSuccess", [], "messages");
yield "\");
},
error: function(jqXHR, textStatus, errorThrown) {
let errorMessage = 'Güncelleme sırasında bilinmeyen bir hata oluştu.';
try {
const response = JSON.parse(jqXHR.responseText);
if (response && response.message) {
errorMessage = response.message;
}
} catch (e) {
console.error(\"JSON Parse Error:\", e);
console.error(\"Server Response:\", jqXHR.responseText);
}
showErrorAlert(errorMessage);
}
});
});
\$('#show-info-btn').on('click', function(e) {
e.preventDefault();
const selectedProductId = \$('#productselect').val();
if (!selectedProductId || selectedProductId <= 0) {
showErrorAlert(\"Detayları görmek için önce bir ürün seçmelisiniz.\");
return;
}
const \$infoContent = \$('#info-details-content');
const \$icon = \$(this).find('i');
\$infoContent.toggle();
if (\$infoContent.is(':visible')) {
\$icon.removeClass('fa-info-circle').addClass('fa-chevron-up');
} else {
\$icon.removeClass('fa-chevron-up').addClass('fa-info-circle');
}
});
// --- GIFT VOUCHER LOGIC ---
// Hediye Kuponu Butonuna Tıklama (Desktop)
\$('#btn-gift-voucher').on('click', function() {
openGiftVoucherModal();
});
// Hediye Kuponu Butonuna Tıklama (Mobile)
\$('#btn-gift-voucher-mobile').on('click', function() {
openGiftVoucherModal();
});
function openGiftVoucherModal() {
const customerId = \$('#sales_form_customer').val();
if (!customerId) {
showErrorAlert('Lütfen önce bir müşteri seçiniz.');
return;
}
// API'den bakiye sorgula
\$.ajax({
url: '/admin/gift-certificates/api/balance/' + customerId,
method: 'GET',
success: function(response) {
if (response.error) {
showErrorAlert(response.error);
return;
}
\$('#voucher-total-balance').text(response.formatted);
\$('#voucher-total-balance').data('balance', response.balance);
\$('#voucher-customer-name').text(response.customerName); // Set customer name
\$('#voucher-use-amount').val(''); // Inputu temizle
// Varsayılan olarak kalanı doldurmayı deneyebiliriz ama kullanıcı girişi daha iyi
const totals = aggregateSoldTotals();
const paymentTotal = convertToDecimal(calculateTotalPaymentAmount());
const remaining = totals.grandTotal.minus(paymentTotal);
if (remaining.greaterThan(0)) {
// Eğer kalan miktar, bakiyeden küçükse kalanı, büyükse bakiyeyi öner
const balance = new Decimal(response.balance);
const suggested = remaining.lessThan(balance) ? remaining : balance;
\$('#voucher-use-amount').val(suggested.toNumber());
}
\$('#gift-voucher-usage-modal').modal('show');
},
error: function() {
showErrorAlert('Müşteri hediye çeki bakiyesi sorgulanırken bir hata oluştu.');
}
});
}
// Hediye Çeki Kullanımını Onayla
\$('#btn-confirm-voucher-usage').on('click', function() {
const amount = parseFloat(\$('#voucher-use-amount').val());
const balance = parseFloat(\$('#voucher-total-balance').data('balance'));
if (isNaN(amount) || amount <= 0) {
showErrorAlert('Lütfen geçerli bir miktar giriniz.');
return;
}
if (amount > balance) {
showErrorAlert('Girdiğiniz miktar, toplam bakiyeden fazla olamaz.');
return;
}
// Ödeme Yöntemini Bul (Hediye Çeki / Gift Voucher)
let voucherMethodId = null;
// 1. Yöntem: Backend'den gelen veriyi kullan (En Sağlam)
// 1. Yöntem: Backend'den gelen veriyi kullan (En Sağlam)
";
// line 3914
if (array_key_exists("paymentMethods", $context)) {
// line 3915
yield " const methods = [
";
// line 3916
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable((isset($context["paymentMethods"]) || array_key_exists("paymentMethods", $context) ? $context["paymentMethods"] : (function () { throw new RuntimeError('Variable "paymentMethods" does not exist.', 3916, $this->source); })()));
foreach ($context['_seq'] as $context["_key"] => $context["pm"]) {
// line 3917
yield " { id: \"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["pm"], "id", [], "any", false, false, false, 3917), "html", null, true);
yield "\", name: \"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["pm"], "name", [], "any", false, false, false, 3917), "js"), "html", null, true);
yield "\" },
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['pm'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 3919
yield " ];
const voucherMethod = methods.find(m =>
(m.name && m.name.toLowerCase().includes('hediye')) ||
(m.name && m.name.toLowerCase().includes('gift')) ||
(m.name && m.name.toLowerCase().includes('kupon'))
);
if (voucherMethod) {
voucherMethodId = voucherMethod.id;
}
";
}
// line 3929
yield "
// 2. Yöntem: Eğer yukarıdaki çalışmazsa DOM'dan bulmayı dene
if (!voucherMethodId) {
const \$methodSelect = \$('select[name=\"paymentMethod[]\"]').first(); // Tablodaki herhangi bir select
\$methodSelect.find('option').each(function() {
const text = \$(this).text().toLowerCase();
if (text.includes('hediye') || text.includes('gift') || text.includes('kupon')) {
voucherMethodId = \$(this).val();
return false; // break
}
});
}
// 3. Yöntem: Modal Select
if (!voucherMethodId) {
const \$modalSelect = \$('#add-payment-modal select[name=\"paymentMethod\"]');
\$modalSelect.find('option').each(function() {
const text = \$(this).text().toLowerCase();
if (text.includes('hediye') || text.includes('gift') || text.includes('kupon')) {
voucherMethodId = \$(this).val();
return false; // break
}
});
}
if (!voucherMethodId) {
// Eğer modal select'inde yoksa, bir de manuel tanımlı ID varsa onu kontrol et (opsiyonel)
// Veya uyarı ver
// Fallback: İlk metodun id'sini alma riskine girmeyelim, kullanıcıya soralım veya hata verelim.
// Ancak genelde bir \"Hediye Çeki\" metodu olmalı.
// Şimdilik hata verelim.
showErrorAlert('Sistemde \"Hediye Çeki\" ödeme yöntemi bulunamadı. Lütfen yönetim panelinden ekleyiniz.');
return;
}
// Yeni ödeme satırı ekle
const payment = {
uuid: crypto.randomUUID(),
amount: amount,
status: \"0\", // Ödendi
paymentDate: new Date().toISOString().split('T')[0], // Bugün
paymentDueDate: new Date().toISOString().split('T')[0], // Bugün
paymentMethod: voucherMethodId,
description: 'Hediye Çeki Kullanımı',
voucherCode: 'AUTO_DEDUCT_FROM_BALANCE' // Backend bunu işleyip ilgili çeklerden düşebilir
};
payments.push(payment);
fetchPaymentForm();
fetchSoldListRows(); // Trigger AVOIR row update
\$('#gift-voucher-usage-modal').modal('hide');
showSuccessAlert(amount + ' TL hediye çeki ödemesi eklendi.');
});
// calculateTotalPaymentAmount fonksiyonunu global scope'ta bulamadıysak buraya ekleyelim veya mevcut olanı kullanalım
function calculateTotalPaymentAmount() {
let total = new Decimal(0);
payments.forEach(function(p) {
total = total.plus(convertToDecimal(p.amount));
});
return total;
}
// --- END GIFT VOUCHER LOGIC ---
});
function createPopup(url, window_name = 'example-popup') {
var width = 1100;
var height = 700;
var screen_width = window.screen.width;
var screen_height = window.screen.height;
var popup_left = (screen_width - width) / 2;
var popup_top = (screen_height - height) / 2;
var popup_window = window.open(url, window_name, 'width=' + width + ',height=' + height + ',left=' + popup_left + ',top=' + popup_top);
}
</script>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
/**
* @codeCoverageIgnore
*/
public function getTemplateName(): string
{
return "admin/sales/index.html.twig";
}
/**
* @codeCoverageIgnore
*/
public function isTraitable(): bool
{
return false;
}
/**
* @codeCoverageIgnore
*/
public function getDebugInfo(): array
{
return array ( 4528 => 3929, 4516 => 3919, 4505 => 3917, 4501 => 3916, 4498 => 3915, 4496 => 3914, 4381 => 3802, 4292 => 3716, 4154 => 3581, 3773 => 3203, 3624 => 3057, 3077 => 2513, 3073 => 2512, 3066 => 2507, 3057 => 2504, 3050 => 2503, 3046 => 2502, 3035 => 2494, 3031 => 2493, 3027 => 2492, 2590 => 2058, 2233 => 1703, 2227 => 1702, 2221 => 1700, 2218 => 1699, 2214 => 1698, 1971 => 1458, 1459 => 949, 1327 => 820, 1317 => 812, 1314 => 811, 1305 => 808, 1301 => 807, 1293 => 802, 1289 => 801, 1285 => 800, 1281 => 799, 1277 => 798, 1273 => 797, 1269 => 796, 1265 => 795, 1261 => 794, 1257 => 793, 1253 => 792, 1249 => 791, 1246 => 790, 1241 => 789, 1239 => 788, 1231 => 783, 1212 => 768, 1199 => 767, 1181 => 759, 1177 => 758, 1170 => 754, 1165 => 752, 1159 => 749, 1149 => 742, 1135 => 731, 1131 => 730, 1123 => 725, 1119 => 724, 1115 => 723, 1107 => 720, 1101 => 719, 1095 => 718, 1084 => 710, 1070 => 699, 1066 => 698, 1058 => 693, 1050 => 688, 1043 => 684, 1031 => 675, 1016 => 663, 1012 => 662, 1004 => 657, 997 => 653, 990 => 649, 983 => 645, 971 => 636, 958 => 626, 954 => 625, 948 => 622, 944 => 621, 940 => 620, 931 => 614, 918 => 604, 914 => 603, 904 => 596, 897 => 592, 887 => 585, 875 => 576, 870 => 574, 866 => 573, 860 => 570, 855 => 568, 847 => 563, 834 => 553, 798 => 520, 790 => 515, 746 => 474, 737 => 468, 724 => 458, 720 => 457, 715 => 455, 711 => 454, 707 => 453, 703 => 452, 699 => 451, 683 => 438, 676 => 434, 668 => 428, 657 => 426, 653 => 425, 649 => 424, 644 => 422, 623 => 404, 619 => 403, 615 => 402, 611 => 401, 607 => 400, 603 => 399, 594 => 392, 592 => 391, 584 => 386, 579 => 384, 574 => 382, 562 => 373, 556 => 369, 553 => 367, 547 => 363, 543 => 362, 539 => 361, 535 => 360, 528 => 356, 524 => 355, 518 => 352, 514 => 351, 506 => 347, 495 => 338, 489 => 334, 480 => 327, 474 => 323, 469 => 320, 466 => 319, 461 => 315, 459 => 312, 447 => 303, 442 => 301, 439 => 300, 437 => 299, 433 => 297, 431 => 296, 426 => 293, 424 => 292, 411 => 282, 406 => 279, 404 => 278, 399 => 275, 397 => 274, 392 => 271, 390 => 270, 384 => 266, 382 => 265, 372 => 257, 359 => 256, 104 => 10, 91 => 9, 79 => 4, 66 => 3, 43 => 1,);
}
public function getSourceContext(): Source
{
return new Source("{% extends 'base.html.twig' %}
{% block title %}
{% trans %}
sales
{% endtrans %}
{% endblock %}
{% block head %}
<style>
/* Keep all form elements at 12px on this page */
select, textarea, button { font-size: 12px !important; }
.form-control, .custom-select { font-size: 12px !important; }
/* Select2 text sizes */
.select2-container .select2-selection__rendered,
.select2-results__option,
.select2-container .select2-search__field { font-size: 12px !important; }
#adder-allocated-quantity,
#adder-quantity-unit {
vertical-align: middle;
}
.product-select-cell {
max-width: 250px;
/* Hücrenin alabileceği maksimum genişlik. İstediğiniz gibi ayarlayın. */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Ekleme Satırı Stilleri */
#adder-row {
background-color: #f8f9fa;
/* Hafif gri arkaplan */
}
#adder-row td {
vertical-align: middle;
padding: 0.25rem; /* Boşluk azaltıldı */
border-top: 2px dashed #e3e6f0;
}
/* Adder satırında hücreler arası yatay boşluğu minimuma indir */
#adder-row .input-group { margin-right: 4px; }
#adder-row .btn-icon, #adder-row .btn { margin-right: 4px; }
/* Yeni Card Header Stili */
.bg-light-blue {
background-color: #f0f3ff !important;
/* Hafif mavi arkaplan */
}
#product-list-table-tbody input {
/*border: none;*/
/*padding: 0;*/
/*margin: 0;*/
background-color: white;
}
.info-span {
color: red;
font-weight: bold;
}
.cost-detail-button {
color: blue;
font-width: bold;
border: none;
background-color: transparent;
}
.attention-icon {
display: inline-block;
vertical-align: middle;
margin-left: 5px;
font-size: 16px;
color: red;
font-weight: bold;
}
.quantity-input-added {
width: 60px;
}
.select2-container {
width: 100% !important;
/* Genişlik uyumluluğu için */
}
/* En kritik CSS düzeltmesi olmasa da, z-index çakışmalarına karşı bir güvencedir.
Asıl çözüm JavaScript'tedir. */
.select2-container--open {
z-index: 1056 !important;
}
.select2-container {
width: 100% !important;
}
.select2-container--open {
z-index: 1056 !important;
}
.select2-container--bootstrap4 .select2-selection--single
{
padding: .50rem !important;
}
.select2-container .select2-selection--single {
height: 28px !important;
}
/* === SEÇİM KARTLARI İÇİN CSS === */
.selection-card-label {
display: block;
cursor: pointer;
width: 100%;
}
.selection-card {
border: 2px solid #e3e6f0;
border-radius: 0.5rem;
padding: 0.50rem;
transition: all 0.2s ease-in-out;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
/* Kartın üzerine gelindiğinde (hover) */
.selection-card:hover {
border-color: #4e73df;
transform: translateY(-2px);
/* Hafif yukarı kalkma efekti */
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
/* Kart içindeki başlık (Fatura, Proforma) */
.selection-title {
font-size: 1.1rem;
font-weight: 500;
color: #5a5c69;
transition: color 0.2s ease-in-out;
}
/* Onay işareti (başlangıçta gizli) */
.checkmark {
font-size: 1.25rem;
color: #4e73df;
opacity: 0;
transform: scale(0.5);
transition: all 0.2s ease-out;
margin-left: 0.75rem;
}
/* SEÇİM YAPILDIĞINDAKİ STİLLER */
/* CSS :has() seçicisi ile, içindeki radio input seçili olan etiketin kartını stillendiriyoruz. */
.selection-card-label:has(input[type=\"radio\"]:checked) .selection-card {
border-color: #4e73df;
background-color: #f0f3ff;
}
/* Seçili kartın başlık rengini değiştir */
.selection-card-label:has(input[type=\"radio\"]:checked) .selection-title {
color: #4e73df;
font-weight: 600;
}
/* Seçili kartın onay işaretini görünür yap */
.selection-card-label:has(input[type=\"radio\"]:checked) .checkmark {
opacity: 1;
transform: scale(1);
}
/* Bilgi satırı için stil */
#info-details-content {
background-color: #f1faff;
/* Hafif mavi arka plan */
border-bottom: 2px dashed #e3e6f0;
/* Altına kesikli çizgi ekler */
}
.info-details-content-class {
background-color: #f1faff;
/* Hafif mavi arka plan */
border-bottom: 2px dashed #e3e6f0;
/* Altına kesikli çizgi ekler */
}
/* Make product table full-width and remove right gaps */
.card-body > .table-responsive { padding-right: 0; }
#product-list-table { width: 100% !important; table-layout: fixed; }
#product-list-table th, #product-list-table td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#product-list-table .product-select-cell select { width: 100%; }
/* Adjust specific column widths to fit viewport */
.summary-row td {
border-top: none;
background-color: #f8f9fc;
padding: 0;
}
.sale-summary-row {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
padding: 0.35rem 0.75rem;
border-radius: 0.35rem;
margin-right: 1.5rem;
}
.sale-summary-row .summary-label {
font-weight: 600;
color: #4b5563;
white-space: nowrap;
}
.sale-summary-row .summary-value {
font-weight: 700;
color: #111827;
text-align: right;
min-width: 120px;
white-space: nowrap;
}
.sale-summary-row.summary-tax .summary-label,
.sale-summary-row.summary-tax .summary-value {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.sale-summary-row.summary-tax .summary-label div,
.sale-summary-row.summary-tax .summary-value div {
white-space: nowrap;
}
.sale-summary-row.summary-total .summary-value {
font-size: 1.25rem;
}
#product-list-table th:nth-child(1), #product-list-table td:nth-child(1) { width: 5%; }
#product-list-table th:nth-child(2), #product-list-table td:nth-child(2) { width: 25%; }
#product-list-table th:nth-child(3), #product-list-table td:nth-child(3) { width: 10%; }
#product-list-table th:nth-child(4), #product-list-table td:nth-child(4) { width: 10%; }
#product-list-table th:nth-child(5), #product-list-table td:nth-child(5) { width: 10%; }
#product-list-table th:nth-child(6), #product-list-table td:nth-child(6) { width: 8%; }
#product-list-table th:nth-child(7), #product-list-table td:nth-child(7) { width: 8%; }
#product-list-table th:nth-child(8), #product-list-table td:nth-child(8) { width: 15%; }
#product-list-table th:nth-child(9), #product-list-table td:nth-child(9) { width: 9%; }
</style>
{% endblock %}
{% block body %}
<div class=\"container-fluid m-0 p-0\">
<div class=\"card shadow mb-4\">
<div class=\"card-header py-3 d-flex justify-content-between align-items-center\">
<div class=\"btn-group\" role=\"group\">
<div class=\"d-md-none\">
<div class=\"dropdown\">
<button class=\"btn btn-secondary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">
{% trans %} actions {% endtrans %}
</button>
<div class=\"dropdown-menu\" aria-labelledby=\"dropdownMenuButton\">
<button type=\"button\" class=\"dropdown-item\" data-toggle=\"modal\" data-target=\"#payments-modal\">
<i class=\"fas fa-money-check-alt mr-1\"></i>
{% trans %}payments{% endtrans %}
</button>
<button type=\"button\" data-toggle=\"modal\" data-target=\"#add-customer-modal\" class=\"dropdown-item\">
<i class=\"fas fa-user-plus mr-1\"></i>
{% trans %}addCustomer{% endtrans %}
</button>
<button type=\"button\" class=\"dropdown-item js-edit-customer\">
<i class=\"fas fa-user-edit mr-1\"></i>
{% trans %}editCustomer{% endtrans %}
</button>
<button type=\"button\" class=\"dropdown-item\" id=\"btn-gift-voucher-mobile\">
<i class=\"fas fa-gift mr-1\"></i>
{{ 'giftVoucher'|trans }}
</button>
</div>
</div>
</div>
<div class=\"d-none d-md-block\">
<div class=\"btn-group\" role=\"group\">
<button type=\"button\" class=\"btn btn-outline-secondary\" data-toggle=\"modal\" data-target=\"#payments-modal\">
<i class=\"fas fa-money-check-alt mr-1\"></i>
{% trans %}payments{% endtrans %}
</button>
<button type=\"button\" data-toggle=\"modal\" data-target=\"#add-customer-modal\" class=\"btn btn-outline-secondary\">
<i class=\"fas fa-user-plus mr-1\"></i>
{% trans %}addCustomer{% endtrans %}
</button>
<button type=\"button\" class=\"btn btn-outline-secondary js-edit-customer\">
{% trans %}editCustomer{% endtrans %}
</button>
<button type=\"button\" class=\"btn btn-outline-secondary\" id=\"btn-gift-voucher\" title=\"{{ 'useGiftVoucher'|trans }}\">
<i class=\"fas fa-gift mr-1\"></i>
{{ 'giftVoucher'|trans }}
</button>
</div>
</div>
</div>
<button type=\"button\" id=\"submitbtn\" class=\"btn btn-success\">
<i class=\"fas fa-save mr-1\"></i>
{% trans %}
save
{% endtrans %}
</button>
</div>
<div class=\"card-body\">
{# FATURA VE PROFORMA SEÇENEKLERİ #}
<div class=\"form-group\">
<label class=\"form-label font-weight-bold\">{{ 'salesType'|trans }}</label>
<div class=\"row\">
{# Fatura Seçeneği #}
<div class=\"col-md-6\">
<label class=\"selection-card-label\">
<input type=\"radio\" name=\"salesType\" class=\"d-none\" value=\"invoice\">
<div class=\"selection-card\">
<span class=\"selection-title\">{% trans %}invoice{% endtrans %}</span>
<i class=\"fas fa-check-circle checkmark\"></i>
</div>
</label>
</div>
{# Proforma Seçeneği #}
<div class=\"col-md-6\">
<label class=\"selection-card-label\">
<input type=\"radio\" name=\"salesType\" class=\"d-none\" value=\"proforma\" checked>
<div class=\"selection-card\">
<span class=\"selection-title\">{% trans %}proforma{% endtrans %}</span>
<i class=\"fas fa-check-circle checkmark\"></i>
</div>
</label>
</div>
</div>
</div>
<div style=\"margin-bottom: 10px\">
{# SATIŞ FORMU başlangıç #}
{{ form_start(form) }}
<div class=\"\">
<div class=\"row\">
<div class=\"col-lg-6 col-md-12\">
{{ form_row(form.totalPurchasePrice) }}
{{ form_row(form.status) }}
</div>
<div class=\"col-lg-6 col-md-12\">
{{ form_row(form.customer) }}
{{ form_row(form.seller) }}
</div>
</div>
<div class=\"row\">
<div class=\"col-lg-3 col-md-6\">{{ form_row(form.salesDate) }}</div>
<div class=\"col-lg-3 col-md-6\">{{ form_row(form.deliveryDate) }}</div>
<div class=\"col-lg-3 col-md-6\">{{ form_row(form.dueDate) }}</div>
<div class=\"col-lg-3 col-md-6\">{{ form_row(form.validDate) }}</div>
</div>
</div>
{# SATIŞ FORMU bitiş #}
{# Ödemeler Tablosu modal #}
<div class=\"modal fade\" id=\"payments-modal\" role=\"dialog\" aria-labelledby=\"paymentsModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog modal-xl\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"paymentsModalLabel\">{% trans %}payments{% endtrans %}</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
<div class=\"card mb-1\">
<div class=\"card-header d-flex align-items-center justify-content-between\">
<div>
<h5 class=\"mb-0\">{% trans %}payments{% endtrans %}</h5>
<div style=\"float: left; width: 250px\">
{% trans %}total{% endtrans %}:
<span id=\"total-amount-span\"></span>
{% trans %}remainingAmount{% endtrans %}:
<span id=\"remainder-amount-span\"></span>
</div>
</div>
<button class=\"btn btn-primary\" type=\"button\" id=\"btn-addPayment\" data-toggle=\"modal\" data-target=\"#add-payment-modal\">
{% trans %}addPayment{% endtrans %}
</button>
</div>
<div class=\"card-body\">
<div>
<table class=\"table\" id=\"payment-list-table\">
<thead>
<tr>
<th>{% trans %}amount{% endtrans %}</th>
<th>{% trans %}status{% endtrans %}</th>
<th>{% trans %}paymentDate{% endtrans %}</th>
<th>{% trans %}dueDate{% endtrans %}</th>
<th>{% trans %}paymentMethod{% endtrans %}</th>
<th>{% trans %}description{% endtrans %}</th>
<th> </th>
</tr>
</thead>
<tbody id=\"payment-list-table-tbody\"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class=\"card border-0 shadow-sm\">
<div class=\"card-header bg-light-blue border-0 py-3\">
<div class=\"row align-items-center\">
<div class=\"col-md-4\">
<label for=\"warehouse-select\" class=\"font-weight-bold mb-1\">{% trans %}warehouse{% endtrans %}</label>
<select id=\"warehouseselect\" class=\"form-control\">
<option value=\"\">{% trans %}selectwarehouse{% endtrans %}</option>
{% for warehouse in warehouses %}
<option value=\"{{ warehouse.id }}\">{{ warehouse.name }}</option>
{% endfor %}
</select>
</div>
<div class=\"col-md-5\"></div>
<div id=\"product-info-box\" class=\"col-md-3\" style=\"display: none;\">
<div class=\"info-box-content\">
<div>
{% trans %}stockQuantity{% endtrans %}:
<strong id=\"info-stock\"></strong>
</div>
<div>
{% trans %}averageCost{% endtrans %}:
<strong id=\"info-cost\"></strong>
</div>
</div>
</div>
</div>
</div>
<div class=\"card-body\" style=\"padding: 0\">
<div class=\"table-responsive\">
<table class=\"table modern-table\" id=\"product-list-table\" style=\"width: 100%;\">
<thead>
<tr>
<th style=\"width: 5%;\">{% trans %}info{% endtrans %}</th>
<th style=\"width: 20%;\">{% trans %}product{% endtrans %}</th>
<th style=\"width: 20%;\">{% trans %}quantity{% endtrans %}</th>
<th style=\"width: 15%;\">{% trans %}unitPrice{% endtrans %}</th>
<th style=\"width: 10%;\">{% trans %}discount{% endtrans %} (%)</th>
<th style=\"width: 10%;\">TVA (%)</th>
<th style=\"width: 15%; text-align: left !important;\" class=\"text-right\">{% trans %}total{% endtrans %}</th>
<th style=\"width: 15%;\" class=\"text-center\">{% trans %}action{% endtrans %}</th>
</tr>
</thead>
<tbody id=\"product-list-table-tbody\">
</tbody>
<tbody id=\"adder-body\">
<tr id=\"adder-row\">
<td>
<button type=\"button\" id=\"show-info-btn\"
class=\"btn btn-sm btn-info btn-icon\" title=\"{{ 'productInfo'|trans }}\">
<i class=\"fas fa-info-circle\"></i>
</button>
</td>
<td class=\"product-select-cell\">
<select id=\"productselect\" class=\"form form-control\">
<option value=\"\">{{ 'selectWarehouseFirst'|trans }}</option>
</select>
</td>
<td>
<div class=\"input-group input-group-sm\" style=\"max-width: 220px;\">
<input type=\"text\" id=\"adder-allocated-quantity\" class=\"form-control\" value=\"0\" min=\"0\">
<div class=\"input-group-append\">
<select id=\"adder-quantity-unit-select\" class=\"custom-select custom-select-sm\">
<option value=\"\">-</option>
</select>
</div>
</div>
</td>
<td><input type=\"text\" id=\"adder-price\" value=\"0\" min=\"0\"
class=\"form-control form-control-sm price-mask\" placeholder=\"Fiyat\">
</td>
<td>
<select name=\"adder-discount-select\" id=\"adder-discount-select\"
class=\"form-control form-control-sm\">
<option value=\"0\">% 0</option>
<option value=\"1\">% 1</option>
<option value=\"2\">% 2</option>
<option value=\"3\">% 3</option>
<option value=\"4\">% 4</option>
<option value=\"5\">% 5</option>
<option value=\"20\">% 20</option>
<option value=\"30\">% 30</option>
<option value=\"100\">% 100</option>
</select>
</td>
<td>
<select name=\"adder-tva-select\" id=\"adder-tva-select\"
class=\"form-control form-control-sm\">
<option value=\"0\" disabled>% 0</option>
<option value=\"10\">% 10</option>
<option value=\"20\">% 20</option>
</select>
</td>
<td class=\"text-right font-weight-bold small\">
<input name=\"adder-total\" id=\"adder-total\" type=\"number\"
class=\"form-control form-control-sm\"
placeholder=\"{% trans %} total {% endtrans %}\"/>
</td>
<td class=\"text-center\">
<div class=\"btn-group\" role=\"group\">
<button type=\"button\" id=\"open-unallocated-modal\" class=\"btn btn-outline-secondary btn-sm\" title=\"{% trans %}unAllocatedQuantity{% endtrans %}\">
<i class=\"fas fa-box-open\"></i>
<span id=\"unalloc-display\" class=\"badge badge-light ml-1\">0</span>
</button>
<button type=\"button\" id=\"add-product-row-btn\"
class=\"btn btn-sm btn-success btn-icon\">
<i class=\"fas fa-plus\"></i>
</button>
</div>
<input type=\"number\" id=\"adder-unallocated-quantity\" class=\"form-control d-none\" value=\"0\" min=\"0\">
<select id=\"adder-unallocated-quantity-unit-select\" class=\"custom-select custom-select-sm d-none\">
<option value=\"\">-</option>
</select>
</td>
</tr>
<tr id=\"info-details-row\">
<td colspan=\"8\" style=\"padding: 0; border-top: none;\">
<div id=\"info-details-content\" class=\"p-3\" style=\"display: none;\">
</div>
</td>
</tr>
</tbody>
<tfoot>
<!-- Rows will be injected by updateTotalAmounts() -->
<tr class=\"summary-row-placeholder\">
<td colspan=\"8\"></td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"add-customer-modal\" role=\"dialog\" aria-labelledby=\"customerModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"customerModalLabel\">{% trans %}addCustomer{% endtrans %}</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
{{ form_start(customerForm) }}
<div class=\"modal-body\">
{{ form_widget(customerForm) }}
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{% trans %}cancel{% endtrans %}</button>
<button type=\"submit\" id=\"btn-customer-save\" class=\"btn btn-primary\">{% trans %}save{% endtrans %}</button>
</div>
{{ form_end(customerForm) }}
</div>
</div>
</div>
<div class=\"modal fade\" id=\"unallocatedModal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"unallocatedModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"unallocatedModalLabel\">{% trans %}unAllocatedQuantity{% endtrans %}</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
<div class=\"form-group\">
<label for=\"modal-unallocated-quantity\">{% trans %}quantity{% endtrans %}</label>
<input type=\"text\" id=\"modal-unallocated-quantity\" class=\"form-control mask-money\" value=\"0\">
</div>
<div class=\"form-group\">
<label for=\"modal-unallocated-unit\">{% trans %}unit{% endtrans %}</label>
<select id=\"modal-unallocated-unit\" class=\"custom-select\">
<option value=\"\">-</option>
</select>
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{% trans %}close{% endtrans %}</button>
<button type=\"button\" id=\"save-unallocated-btn\" class=\"btn btn-primary\">{% trans %}save{% endtrans %}</button>
</div>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"add-payment-modal\" role=\"dialog\" aria-labelledby=\"paymentModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"paymentModalLabel\">{% trans %}addPayment{% endtrans %}</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
{{ form_widget(paymentForm) }}
{{ form_start(paymentForm) }}
{{ form_end(paymentForm) }}
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{% trans %}cancel{% endtrans %}</button>
<button type=\"button\" id=\"save-payment-btn\" class=\"btn btn-primary\">{% trans %}save{% endtrans %}</button>
</div>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"updateCustomerModal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"updateModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"updateModalLabel\">{% trans %}updateCustomerInfoMessage{% endtrans %}</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<form id=\"updateCustomerForm\">
<div class=\"modal-body\">
<input type=\"hidden\" id=\"update_customer_id\">
<div class=\"form-group\">
<label for=\"update_customer_fullName\">{% trans %}fullName{% endtrans %}</label>
<input type=\"text\" class=\"form-control\" id=\"update_customer_fullName\" required>
</div>
<div class=\"form-group\">
<label for=\"update_customer_email\">{% trans %}email{% endtrans %}</label>
<input type=\"email\" class=\"form-control\" id=\"update_customer_email\">
</div>
<div class=\"form-group\">
<label for=\"update_customer_phone\">{% trans %}phone{% endtrans %}</label>
<input type=\"text\" class=\"form-control\" id=\"update_customer_phone\">
</div>
<div class=\"form-group\">
<label for=\"update_customer_address\">{% trans %}address{% endtrans %}</label>
<textarea class=\"form-control\" id=\"update_customer_address\" rows=\"3\"></textarea>
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{% trans %}close{% endtrans %}</button>
<button type=\"submit\" class=\"btn btn-primary\">{% trans %}save{% endtrans %}</button>
</div>
</form>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"updateServiceModal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"updateServiceModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"updateServiceModalLabel\">{% trans %}updateServiceInfoMessage{% endtrans %}</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<form id=\"updateServiceForm\" onsubmit=\"return false;\">
<div class=\"modal-body\">
<input type=\"hidden\" id=\"update_service_id\">
<div class=\"form-group\">
<label for=\"update_service_name\">{% trans %}serviceName{% endtrans %}</label>
<input type=\"text\" class=\"form-control\" id=\"update_service_name\">
</div>
<div class=\"form-group\">
<label for=\"update_service_cost\">{% trans %}cost{% endtrans %}</label>
<input type=\"text\" value=\"0.00\" class=\"form-control mask-money\" id=\"update_service_cost\" required>
</div>
<div class=\"form-group\">
<label for=\"update_service_description\">{% trans %}description{% endtrans %}</label>
<input type=\"text\" class=\"form-control\" id=\"update_service_description\">
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{% trans %}close{% endtrans %}</button>
<button type=\"button\" class=\"btn btn-primary\" id=\"service_modal_save_changes_btn\">{% trans %}save{% endtrans %}</button>
</div>
</form>
</div>
</div>
</div>
<div class=\"modal fade\" id=\"autoPaymentModal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"autoPaymentModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog modal-dialog-centered\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"autoPaymentModalLabel\">{{ 'autoPaymentConfirmation'|trans }}</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
<p id=\"auto-payment-message\" class=\"mb-3\"></p>
<div class=\"border rounded px-3 py-2 bg-light\">
<div class=\"d-flex justify-content-between\"><span>{{ 'salesTotal'|trans }}</span><span id=\"auto-payment-sale-total\">0,00 {{ 'currency.symbol'|trans }}</span></div>
<div class=\"d-flex justify-content-between\"><span>{{ 'currentPayments'|trans }}</span><span id=\"auto-payment-current-payments\">0,00 {{ 'currency.symbol'|trans }}</span></div>
<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>
<hr>
<div class=\"small\">
<div><strong>{{ 'paymentStatusLabel'|trans }}:</strong> <span id=\"auto-payment-status\"></span></div>
<div><strong>{{ 'paymentTypeLabel'|trans }}:</strong> <span id=\"auto-payment-method\"></span></div>
<div><strong>{{ 'description'|trans }}:</strong> <span id=\"auto-payment-description\"></span></div>
</div>
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{{ 'cancel'|trans }}</button>
<button type=\"button\" class=\"btn btn-primary\" id=\"confirm-auto-payment\">{{ 'confirmAction'|trans }}</button>
</div>
</div>
</div>
</div>
<!-- Hediye Çeki Kullanım Modalı -->
<div class=\"modal fade\" id=\"gift-voucher-usage-modal\" tabindex=\"-1\" role=\"dialog\" aria-labelledby=\"giftVoucherUsageModalLabel\" aria-hidden=\"true\">
<div class=\"modal-dialog modal-dialog-centered\" role=\"document\">
<div class=\"modal-content\">
<div class=\"modal-header\">
<h5 class=\"modal-title\" id=\"giftVoucherUsageModalLabel\">{{ 'giftVoucherUsage'|trans }}</h5>
<button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\">
<span aria-hidden=\"true\">×</span>
</button>
</div>
<div class=\"modal-body\">
<div class=\"alert alert-info\">
<strong><span id=\"voucher-customer-name\"></span></strong>{{ 'voucherBalanceInfoSuffix'|trans }}: <strong id=\"voucher-total-balance\">0.00 TL</strong>
</div>
<div class=\"form-group\">
<label for=\"voucher-use-amount\">{{ 'amountToUse'|trans }}</label>
<input type=\"number\" id=\"voucher-use-amount\" class=\"form-control\" placeholder=\"0.00\" min=\"0\" step=\"0.01\">
<small class=\"form-text text-muted\">{{ 'enterAmountToUse'|trans }}</small>
</div>
</div>
<div class=\"modal-footer\">
<button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">{{ 'cancel'|trans }}</button>
<button type=\"button\" class=\"btn btn-primary\" id=\"btn-confirm-voucher-usage\">{{ 'confirmAndAdd'|trans }}</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascript %}
<script src=\"{{ asset('/decimal.js') }}\"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const saleDateInput = document.getElementById('sales_form_salesDate');
if (saleDateInput && !saleDateInput.value) {
const now = new Date();
const localDate = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().split('T')[0];
saleDateInput.value = localDate;
saleDateInput.dispatchEvent(new Event('change', { bubbles: true }));
}
});
\$(document).ready(function() {
applyMasks();
const isDuplicatedProforma = {{ sales is defined and sales.id is not null ? 'true' : 'false' }};
const duplicatedSoldItems = (function(){
if(!isDuplicatedProforma){ return []; }
const items = [];
{% if sales is defined and sales.productsSolds is defined %}
{% for ps in sales.productsSolds %}
items.push({
productid: {{ ps.product ? ps.product.id : 0 }},
productName: '{{ ps.productName|default(ps.product ? ps.product.name : '')|e('js') }}',
code: '{{ ps.product ? ps.product.code|e('js') : '' }}',
measurement: '{{ ps.measurement is not null ? ps.measurement|e('js') : (ps.product and ps.product.measurement ? ps.product.measurement.name|e('js') : '') }}',
quantity: {{ ps.quantity is not null ? ps.quantity|number_format(2, '.', '') : '0' }},
unAllocatedQuantity: {{ ps.unAllocatedQuantity is not null ? ps.unAllocatedQuantity|number_format(2, '.', '') : '0' }},
unitPrice: {{ ps.totalUnitPurchasePrice is not null ? ps.totalUnitPurchasePrice|number_format(2, '.', '') : '0' }},
tax: {{ ps.tax is not null ? ps.tax|number_format(2, '.', '') : '0' }},
discount: {{ ps.discount is not null ? ps.discount|number_format(2, '.', '') : '0' }},
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')) }},
totalPrice: {{ ps.totalPrice }},
warehouse: {{ sales.warehouse ? sales.warehouse.id : 0 }},
thickness: 0,
width: 0,
height: 0,
cost: 0,
productType: '{{ ps.product and ps.product.productTypeEnum ? ps.product.productTypeEnum.name|e('js') : '' }}',
selectedUnitId: {{ ps.selectedUnit ? ps.selectedUnit.id : 'null' }}
});
{% endfor %}
{% endif %}
return items;
})();
// TODO: Duplicated items
async function importDuplicatedSoldItemsIfNeeded(){
await getProductsFromWarehouse();
if(!isDuplicatedProforma || duplicatedSoldItems.length === 0){
return;
}
const targetWarehouseId = {{ sales is defined and sales.warehouse ? sales.warehouse.id : 0 }};
if(targetWarehouseId){
\$('#warehouseselect').val(String(targetWarehouseId));
\$('#warehouseselect').trigger('change');
}
const start = Date.now();
const timeoutMs = 15000;
const timer = setInterval(function(){
if(Array.isArray(products) && products.length > 0){
clearInterval(timer);
try{
duplicatedSoldItems.forEach(function(item){
const p = products.find(function(prod){ return prod.productid === item.productid; });
if(p){
selectProduct(p.productid);
}
addProductToSoldList(
item.productid,
item.productName,
item.code,
item.measurement,
item.quantity,
item.unitPrice,
item.tax,
item.discount,
item.totalPurchasePrice,
item.warehouse,
item.unAllocatedQuantity,
item.thickness,
item.width,
item.height,
item.cost,
item.productType,
item.selectedUnitId
);
});
fetchSoldListRows();
updateTotalAmounts();
\$('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
fetchPaymentForm();
fetchStocks();
}catch(e){
console.error('Duplicate import error', e);
}
}else if(Date.now() - start > timeoutMs){
clearInterval(timer);
console.warn('Timed out waiting for products to load');
}
}, 200);
}
if(isDuplicatedProforma){
importDuplicatedSoldItemsIfNeeded();
}
// =============================================================================
// INITIALIZATION & GLOBAL VARIABLES
// =============================================================================
// Collapse sidebar only on this page
\$('body').addClass('sidebar-toggled');
\$('#accordionSidebar').addClass('toggled');
// Product data structure
let product = {
code: \"\",
id: 0,
length: 0,
measurement: \"\",
name: \"\",
priceFob: 0,
priceNavlun: 0,
productid: 0,
purchaseTotalAmount: 0,
quantity: 0,
thickness: 0,
totalQuantity: 0,
totalUnitPrice: 0,
width: 0,
cost: 0,
measurementUnit: \"\",
stock: 0,
warehouseid: 0,
selected: false,
}
let products = [];
// Sold product data structure
let soldProduct = {
productid: 0,
productName: \"\",
code: \"\",
measurement: \"\",
quantity: 0,
baseUnitQuantity: 0,
unAllocatedQuantity: 0,
unitPrice: 0,
tax: 0,
discount: 0,
totalPurchasePrice: 0,
warehouse: 0,
}
let soldList = [];
const MONEY_ROUNDING_MODE = Decimal.ROUND_HALF_UP;
// Payment data structure
let payment = {
uuid: 0,
id: 0,
amount: 0,
status: 0,
paymentDate: new Date(),
paymentDueDate: new Date(),
paymentMethod: 0,
description: 0,
}
let payments = []
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
function formatCurrency(amount) {
if (amount instanceof Decimal) {
amount = amount.toNumber();
}
return addCommas(amount.toFixed(2)) + ' {{ 'currency.symbol'|trans }}';
}
// Convert entered quantity in selected unit to product base unit quantity
function computeBaseUnitQuantity(productId, selectedUnitId, quantity, onDone){
try{
const product = products.find(p => p.productid === productId);
if(!product){ onDone(quantity); return; }
// If unit not selected or already base unit, fallback to same quantity
if(!selectedUnitId || String(selectedUnitId) === '' || String(selectedUnitId) === String(product.measurementUnitId)){
onDone(quantity);
return;
}
\$.ajax({
url: '/admin/measurement/convert-to-product-unit',
method: 'POST',
dataType: 'json',
data: {
productId: productId,
fromUnitId: selectedUnitId,
quantity: quantity
},
success: function(resp){
if(resp && resp.success){
onDone(parseFloat(resp.result));
} else {
onDone(quantity);
}
},
error: function(){ onDone(quantity); }
});
}catch(e){ onDone(quantity); }
}
// Check if product already exists in sold list with same parameters
function issetInSoldList(productid, quantity, unitPrice, tax, discount, totalPurchasePrice) {
const foundProduct = soldList.find(product =>
product.productid === productid &&
product.quantity === quantity &&
product.unitPrice === unitPrice &&
product.tax === tax &&
product.discount === discount &&
product.totalPurchasePrice === totalPurchasePrice
);
return !!foundProduct;
}
function applyMasks() {
\$('.mask-money').mask(\"##0.00\", { reverse: true });
}
// Convert various input types to Decimal for precise calculations
function convertToDecimal(value) {
try {
if (value instanceof Decimal) {
return value;
}
if (value === undefined || value === null) {
return new Decimal(0);
}
if (typeof value === 'number') {
if (!isFinite(value) || isNaN(value)) {
return new Decimal(0);
}
return new Decimal(value);
}
if (typeof value === 'string') {
var sanitized = value.replace(/[^0-9.,-]/g, '').replace(/,/g, '').trim();
if (sanitized === '' || sanitized === '-' || sanitized === '.' || sanitized === '-.') {
return new Decimal(0);
}
return new Decimal(sanitized);
}
return new Decimal(0);
} catch (e) {
return new Decimal(0);
}
}
// Round value to specified decimal places (default 2)
function roundToDecimal(value, decimalPlaces = 2) {
const decimalValue = new Decimal(value);
const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_UP);
return roundedValue.toNumber();
}
// Round stock values down to prevent overselling
function roundStockToDecimal(value, decimalPlaces = 2) {
const decimalValue = new Decimal(value);
const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_DOWN);
return roundedValue.toNumber();
}
// Round to nearest cent for currency calculations
function roundToNearestCent(amount) {
return Math.round(amount * 100) / 100;
}
// Round to two decimal places
function roundToTwoDecimals(number) {
var factor = Math.pow(10, 2);
return (Math.round(number * factor) / factor).toFixed(2);
}
// Validate numeric input
function validate(s) {
var rgx = /^[0-9]*\\.?[0-9]*\$/;
return s.match(rgx);
}
// Convert string to float, handling commas
function convertToFloat(value) {
if (typeof value === \"string\") {
value = value.replace(',', '');
}
return parseFloat(value);
}
// Add thousand separators to numbers
function addCommas(number) {
let decimals = 2;
number = (number + '').replace(/[^0-9+\\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = ',';
var dec = '.';
var s = '';
var toFixedFix = function(n, prec) {
var k = Math.pow(10, prec);
return '' + (Math.round(n * k) / k).toFixed(prec);
};
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (s[0].length > 3) {
s[0] = s[0].replace(/\\B(?=(?:\\d{3})+(?!\\d))/g, sep);
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
// Create popup window with specified dimensions
function createPopup(url, window_name = 'example-popup') {
var width = 1100;
var height = 700;
var screen_width = window.screen.width;
var screen_height = window.screen.height;
var popup_left = (screen_width - width) / 2;
var popup_top = (screen_height - height) / 2;
var popup_window = window.open(url, window_name, 'width=' + width + ',height=' + height + ',left=' + popup_left + ',top=' + popup_top);
}
// Show error alert using SweetAlert
function showErrorAlert(message) {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: '' + message,
background: 'white',
});
}
// Show success alert using SweetAlert
function showSuccessAlert(message) {
Swal.fire({
icon: 'success',
title: 'Başarılı',
text: '' + message,
background: 'white',
});
}
// =============================================================================
// PRODUCT MANAGEMENT FUNCTIONS
// =============================================================================
// Add product to sold list with validation
function addProductToSoldList(
productid,
productName,
code,
measurement,
quantity,
unitPrice,
tax,
discount,
totalPurchasePrice,
warehouseid,
unAllocatedQuantity,
thickness,
width,
height,
cost,
productType,
selectedUnitId = null
) {
var newQuantity = convertToDecimal(quantity).toNumber();
var newUnAllocatedQuantity = convertToDecimal(unAllocatedQuantity).toNumber();
var newPrice = convertToDecimal(unitPrice).toNumber();
var newTax = convertToDecimal(tax).toNumber();
var newDiscount = convertToDecimal(discount).toNumber();
var newTotalPurchasePrice = convertToDecimal(totalPurchasePrice).toNumber();
if (!issetInSoldList(productid, newQuantity, newPrice, newTax, newDiscount, newTotalPurchasePrice)) {
let productSold = {
uuid: crypto.randomUUID(),
productid: productid,
productName: productName,
code: code,
measurement: measurement,
quantity: newQuantity,
baseUnitQuantity: newQuantity,
unAllocatedQuantity: newUnAllocatedQuantity,
unitPrice: newPrice,
tax: newTax,
discount: newDiscount,
totalPurchasePrice: newTotalPurchasePrice,
warehouse: warehouseid,
thickness: thickness,
width: width,
height: height,
cost: cost,
productType: productType,
selectedUnitId: selectedUnitId,
}
soldList.push(productSold);
// compute base unit quantity asynchronously and update the item
computeBaseUnitQuantity(productid, selectedUnitId, newQuantity, function(baseQty){
const item = soldList.find(i => i.uuid === productSold.uuid);
if(item){ item.baseUnitQuantity = convertToDecimal(baseQty || newQuantity).toNumber(); }
});
fetchStocks();
fetchSoldListRows();
fetchPaymentForm();
\$('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
return productSold;
} else {
console.log(\"List + \")
console.log(soldList)
throw new Error(\"Ürün zaten listede mevcut !\")
}
}
// Remove product from sold list by UUID
function deleteProductInSoldList(uuid) {
soldList = soldList.filter(product =>
product.uuid !== uuid
);
fetchSoldListRows();
}
// Mark product as selected and unselect others
function selectProduct(productId) {
products.forEach(product => product.selected = false);
let product = products.find(product => product.productid === productId);
if (product) {
product.selected = true;
}
return products;
}
// Get currently selected product
function getSelectedProduct() {
return products.find(product => product.selected === true);
}
// Update product dropdown with available products
function updateProductSelect() {
\$('#productselect').empty();
\$('#productselect').append('<option value=\"0\" data-image=\"image-not-found.webp\">Ürün Seçin</option>');
\$.each(products, function(index, value) {
var image = value.image;
var option = '<option value=\"' + value.id + '\" data-image=\"' + image + '\" class=\"stockoption\">' + value.name + ' - ' + value.code + ' - ' + value.measurement + ' - ' + value.quantity + '</option>';
\$('#productselect').append(option);
});
\$('#productselect').attr('disabled', false);
}
// Load products from selected warehouse
function getProductsFromWarehouse() {
var id = \$('#warehouseselect option:selected').val();
if (!id) {
return;
}
\$.ajax({
url: \"/warehouse-stocks/\" + id,
dataType: 'JSON',
method: 'GET',
success: function(response) {
if (response.length > 0) {
if (soldList.length <= 0) {
products = [];
\$.each(response, function(index, product) {
let productObj = {
code: product.code,
id: product.id,
image: product.image,
length: 0,
measurement: product.measurement,
name: product.name,
priceFob: 0,
priceNavlun: 0,
productid: product.productid,
purchaseTotalAmount: 0,
quantity: product.totalQuantity,
unAllocatedQuantity: 0,
thickness: 0,
totalQuantity: product.totalQuantity,
totalUnitPrice: 0,
width: 0,
cost: 0,
measurementUnit: product.measurementUnit,
measurementUnitId: product.measurementUnitId,
stock: 0,
warehouseid: \$('#warehouseselect option:selected').val(),
convertibleUnits: [],
productType: product.productTypeEnum,
};
products.push(productObj);
});
updateProductSelect();
}
} else {
showErrorAlert('Seçmiş olduğunuz depoda hiç ürün stoğu bulunmamaktadır.');
}
},
error: function(error) {
showErrorAlert('Bilinmeyen bir sistem hatası oluştu lütfen tekrar deneyin.');
}
});
}
// Update stock quantities based on sold items
function fetchStocks() {
// const groupedSoldList = soldList.reduce((acc, soldProduct) => {
// if (!acc[soldProduct.productid]) {
// acc[soldProduct.productid] = 0;
// }
// acc[soldProduct.productid] += convertToDecimal(soldProduct.quantity).toNumber();
// return acc;
// }, {});
const groupedSoldList = soldList.reduce((acc, soldProduct) => {
const serviceTypes = [\"INSTALLATION_SERVICE\", \"MAINTENANCE_SERVICE\", \"CONSULTATION_SERVICE\", \"TREE\"];
const productTypeKey = soldProduct.productType ? (soldProduct.productType.key || soldProduct.productType.name) : null;
const isService = productTypeKey ? serviceTypes.includes(productTypeKey) : false;
if (isService) {
return acc;
}
if (!acc[soldProduct.productid]) {
acc[soldProduct.productid] = 0;
}
const quantityToDeduct = soldProduct.baseUnitQuantity || soldProduct.quantity;
acc[soldProduct.productid] += convertToDecimal(quantityToDeduct).toNumber();
return acc;
}, {});
products.forEach(product => {
if (groupedSoldList[product.productid]) {
const totalSoldQuantity = groupedSoldList[product.productid];
product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity - totalSoldQuantity).toNumber());
} else {
product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity).toNumber());
}
});
calculateAndWriteDiffBetweenPaymentAndSolds();
}
// Validate stock quantity before adding to sold list
// function checkStockQuantity(quantity) {
// let product = products.find(p => p.productid === parseInt(productId, 10));
// const productType = product.productType.key;
// if(productType === \"INSTALLATION_SERVICE\" || productType === \"MAINTENANCE_SERVICE\" || productType === \"CONSULTATION_SERVICE\" || productType === 'TREE'){
//
// \$('#updateServiceModal').modal('show');
//
// }else{
// if (convertToFloat(quantity) > product.quantity) {
// throw new Error('Yetersiz stok. Ekeleyebileceğiniz maksimum stok: ' + product.quantity);
// resetAddProductForm();
// }
// }
//
// }
function checkStockQuantity() {
return new Promise((resolve, reject) => {
const selectedProduct = getSelectedProduct();
if (!selectedProduct) {
return reject(new Error('Lütfen bir ürün seçin.'));
}
const productType = selectedProduct.productType.key;
const isService = [\"INSTALLATION_SERVICE\", \"MAINTENANCE_SERVICE\", \"CONSULTATION_SERVICE\", \"TREE\"].includes(productType);
if (isService) {
// Eğer ürün bir hizmet ise, stok kontrolü yapma, işlemi onayla.
return resolve();
}
const quantityStr = \$('#adder-allocated-quantity').val();
const fromUnitId = \$('#adder-quantity-unit-select').val();
const requestedQuantity = convertToDecimal(quantityStr).toNumber();
// Eğer birim seçilmemişse veya miktar 0 ise, miktar kadarını stokla karşılaştır.
if (!fromUnitId || fromUnitId === '') {
if (requestedQuantity > selectedProduct.quantity) {
return reject(new Error(`Yetersiz stok. Eklenebilecek maksimum miktar: \${selectedProduct.quantity} \${selectedProduct.measurementUnit}`));
}
return resolve(requestedQuantity); // Temel birimdeki miktarı döndür
}
// Seçilen birimdeki miktarı, ürünün temel birimine çevir.
\$.ajax({
url: '/admin/measurement/convert-to-product-unit',
method: 'POST',
dataType: 'json',
data: {
productId: selectedProduct.productid,
fromUnitId: fromUnitId,
quantity: requestedQuantity
},
success: function(resp) {
if (resp && resp.success) {
const requestedBaseQuantity = convertToDecimal(resp.result).toNumber();
// Temel birimdeki talep edilen miktar, stoktan büyük mü diye kontrol et.
if (requestedBaseQuantity > selectedProduct.quantity) {
reject(new Error(`Yetersiz stok. Talep edilen miktar (\${quantityStr} \${\$('#adder-quantity-unit-select option:selected').text()}), stoktaki \${selectedProduct.quantity} \${selectedProduct.measurementUnit}'den fazla.`));
} else {
resolve(requestedBaseQuantity); // Kontrol başarılı, temel birimdeki miktarı döndür.
}
} else {
reject(new Error('Birim dönüştürme sırasında bir hata oluştu. Stok kontrol edilemedi.'));
}
},
error: function() {
reject(new Error('Stok kontrolü sırasında sunucuya ulaşılamadı.'));
}
});
});
}
// Get product cost information from server
function getProductCost(pid) {
const url = '/product-cost/' + pid + '/' + getSelectedProduct().warehouseid;
\$.ajax({
url: \"/product-cost-detail/\" + pid + '?warehouse=' + getSelectedProduct().warehouseid,
method: \"POST\",
dataType: \"JSON\",
success: function(data) {
var product = getSelectedProduct();
product.cost = data.cost;
product.measurementUnit = data.measurement;
\$('.stock-info-span').html(data.stock.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" \" + data.measurement);
\$('.cost-info-span').html(data.cost.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" €\");
\$('.measurement-unit-info-span').html(data.measurement);
\$('#hidden-cost-input').val(data.cost);
const detailsHtml = `
<div class=\"row align-items-center\">
<div class=\"col-md-3\">\${product.name}</div>
<div class=\"col-md-3\">
<strong>
<button type=\"button\" class=\"btn btn-link btn-sm p-0 cost-popup-btn\" data-url=\"\${url}\">Maliyet: \${data.cost.toFixed(2)} €</button>
</strong>
</div>
<div class=\"col-md-3\"><strong>Stok:</strong> \${data.stock.toFixed(2)} \${data.measurement}</div>
<div class=\"col-md-3\"><strong>Birim:</strong> \${data.measurement}</div>
</div>
`;
\$('#info-details-content').html(detailsHtml);
return data;
},
error: function(data) {
\$('#info-details-content').html('<div class=\"text-danger\">Ürün maliyet bilgileri yüklenemedi.</div>');
}
});
}
// Generate HTML for a product row (Standard or AVOIR)
function generateProductRowHtml(value, options = {}) {
const isAvoir = options.isAvoir || false;
const readonly = isAvoir ? 'readonly disabled' : '';
const inputReadonly = isAvoir ? 'readonly' : ''; // For inputs we might want just readonly, not disabled
const rowClass = isAvoir ? 'bg-light text-danger' : '';
const uuid = value.uuid;
// Render Service Button (only for standard rows)
const serviceBtnHtml = !isAvoir ? renderServiceEditBtn(value) : '';
// Actions
let actionButtonsHtml = '';
if (!isAvoir) {
actionButtonsHtml = `
<div class=\"btn-group\" role=\"group\">
\${serviceBtnHtml}
<button type=\"button\" class=\"btn btn-outline-secondary btn-sm open-unallocated-modal-row\"
data-uuid=\"\${uuid}\" title=\"{% trans %}unAllocatedQuantity{% endtrans %}\">
<i class=\"fas fa-box-open\"></i>
<span class=\"badge badge-light ml-1 unalloc-display\">\${value.unAllocatedQuantity.toFixed(2)}</span>
</button>
<button type=\"button\" class=\"btn btn-danger btn-sm delete-row-button\"
data-uuid=\"\${uuid}\" title=\"Sil\">
<i class=\"fas fa-trash\"></i>
</button>
</div>
`;
} else {
// Empty for AVOIR
actionButtonsHtml = '';
}
// Info Button
let infoButtonHtml = '';
if (!isAvoir) {
infoButtonHtml = `
<button type=\"button\" class=\"btn btn-sm btn-info btn-icon show-row-info-btn\"
data-uuid=\"\${uuid}\" title=\"Ürün Bilgileri\">
<i class=\"fas fa-info-circle\"></i>
</button>
`;
}
// Product Select / Name
let productSelectHtml = '';
if (isAvoir) {
// For AVOIR, we just show a static input or disabled select mimicking the look
productSelectHtml = `
<input type=\"text\" class=\"form-control text-danger font-weight-bold\" value=\"AVOIR\" readonly>
`;
} else {
let productOptions = '';
const serviceTypes = ['INSTALLATION_SERVICE','MAINTENANCE_SERVICE','CONSULTATION_SERVICE','TREE'];
const isServiceRow = (function(pt){
try {
if (!pt) return false;
if (typeof pt === 'string') return serviceTypes.includes(pt);
const key = pt.key || pt.name;
return serviceTypes.includes(key);
} catch(e) { return false; }
})(value.productType);
products.forEach(function(product) {
const isSelected = product.productid === value.productid ? 'selected' : '';
const displayName = (isSelected && isServiceRow && value.productName) ? value.productName : product.name;
productOptions += `<option value=\"\${product.productid}\" \${isSelected}>\${displayName} - \${product.measurement} - \${product.quantity} - \${product.code}</option>`;
});
productSelectHtml = `
<select name=\"productId[]\" class=\"form-control product-select-row\" data-uuid=\"\${uuid}\">
\${productOptions}
</select>
`;
}
// Unallocated Hidden Inputs
const unAllocatedHidens = !isAvoir ? `
<input type=\"hidden\" name=\"warehouseid[]\" value=\"\${value.warehouse}\">
<input type=\"hidden\" name=\"length[]\" value=\"\${value.length || ''}\">
<input type=\"hidden\" name=\"width[]\" value=\"\${value.width || ''}\">
<input type=\"hidden\" name=\"thickness[]\" value=\"\${value.thickness || ''}\">
<input type=\"hidden\" name=\"cost[]\" value=\"\${value.cost || ''}\">
<input type=\"hidden\" name=\"unAllocatedQuantity[]\" class=\"unallocated-quantity-hidden\" value=\"\${value.unAllocatedQuantity}\" data-uuid=\"\${uuid}\">
<input type=\"hidden\" name=\"unAllocatedQuantityUnit[]\" class=\"unallocated-unit-hidden\" value=\"\" data-uuid=\"\${uuid}\">
` : '';
// Quantity Unit Select
let quantityUnitSelectHtml = '';
if(isAvoir){
quantityUnitSelectHtml = `
<select class=\"custom-select custom-select-sm\" disabled>
<option>-</option>
</select>
`;
} else {
quantityUnitSelectHtml = `
<select class=\"custom-select custom-select-sm quantity-unit-select\"
name=\"quantityUnit[]\" data-uuid=\"\${uuid}\">
<option value=\"\">-</option>
</select>
`;
}
// Discount Options
let discountOptionsHtml = '';
const discounts = [0, 1, 2, 3, 4, 5, 20, 30, 100];
discounts.forEach(function(d){
discountOptionsHtml += `<option value=\"\${d}\" \${value.discount === d ? 'selected' : ''}>% \${d}</option>`;
});
// Tax Options
let taxOptionsHtml = '';
const taxes = [0, 10, 20];
taxes.forEach(function(t){
const disabledAttr = t === 0 ? 'disabled' : '';
taxOptionsHtml += `<option value=\"\${t}\" \${value.tax === t ? 'selected' : ''} \${disabledAttr}>% \${t}</option>`;
});
const quantityVal = value.quantity !== undefined ? value.quantity.toFixed(2) : '1.00';
const unitPriceVal = value.unitPrice !== undefined ? value.unitPrice.toFixed(2) : '0.00';
const totalPriceVal = value.totalPurchasePrice !== undefined ? value.totalPurchasePrice.toFixed(2) : '0.00';
// Only standard rows get the name=\"...\" attributes to be submitted
const nameAttr = isAvoir ? '' : 'name=';
return `
<tr id=\"\${uuid}\" data-product-id=\"\${value.productid || ''}\" class=\"\${rowClass}\">
<td>\${infoButtonHtml}</td>
<td class=\"product-select-cell\">
\${productSelectHtml}
\${unAllocatedHidens}
</td>
<td>
<div class=\"input-group input-group-sm\" style=\"max-width: 220px;\">
<input type=\"number\" \${!isAvoir ? 'name=\"quantity[]\"' : ''}
class=\"form-control quantity-input-row mask-money \${isAvoir ? 'text-danger' : ''}\"
value=\"\${quantityVal}\"
min=\"0\" step=\"0.01\" data-uuid=\"\${uuid}\" \${inputReadonly}>
<div class=\"input-group-append\">
\${quantityUnitSelectHtml}
</div>
</div>
</td>
<td>
<input type=\"text\" \${!isAvoir ? 'name=\"unitPrice[]\"' : ''}
class=\"form-control form-control-sm price-input-row mask-money \${isAvoir ? 'text-danger' : ''}\"
value=\"\${unitPriceVal}\"
min=\"0\" step=\"0.01\" data-uuid=\"\${uuid}\"
placeholder=\"Birim Fiyat\" \${inputReadonly}>
</td>
<td>
<select \${!isAvoir ? 'name=\"discount[]\"' : ''} class=\"form-control form-control-sm discount-select-row\"
data-uuid=\"\${uuid}\" \${readonly}>
\${discountOptionsHtml}
</select>
</td>
<td>
<select \${!isAvoir ? 'name=\"tax[]\"' : ''} class=\"form-control form-control-sm tax-select-row\"
data-uuid=\"\${uuid}\" \${readonly}>
\${taxOptionsHtml}
</select>
</td>
<td class=\"text-right\">
<input \${!isAvoir ? 'name=\"totalPrice[]\"' : ''} type=\"text\"
class=\"form-control form-control-sm total-input-row mask-money \${isAvoir ? 'text-danger font-weight-bold' : ''}\"
value=\"\${totalPriceVal}\"
data-uuid=\"\${uuid}\"
placeholder=\"Toplam\" \${inputReadonly}/>
</td>
<td class=\"text-center\">
\${actionButtonsHtml}
</td>
</tr>
\${!isAvoir ? `
<tr class=\"info-details-row-class\" data-uuid=\"\${uuid}\" style=\"display: none;\">
<td colspan=\"8\" style=\"padding: 0; border-top: none;\">
<div class=\"info-details-content-class p-3\" style=\"display: none;\">
<div class=\"row\">
<div class=\"col-md-3\"><strong>Ürün Adı:</strong> \${value.productName}</div>
<div class=\"col-md-3\"><strong>Kod:</strong> \${value.code}</div>
<div class=\"col-md-3\"><strong>Ölçü:</strong> \${value.measurement}</div>
<div class=\"col-md-3\"><strong>Uzunluk:</strong> \${value.length || '-'}</div>
</div>
<div class=\"row mt-2\">
<div class=\"col-md-3\"><strong>Genişlik:</strong> \${value.width || '-'}</div>
<div class=\"col-md-3\"><strong>Kalınlık:</strong> \${value.thickness || '-'}</div>
<div class=\"col-md-3\"><strong>Maliyet:</strong> \${(value.cost || 0).toFixed(2)} €</div>
<div class=\"col-md-3\"><strong>Depo:</strong> \${value.warehouse}</div>
</div>
</div>
</td>
</tr>` : ''}
`;
}
// Render sold products table rows with complete editing capabilities
// TODO: Seçilen ürün eğer hizmet ise dropdown da hizmet adını kaydedildiği şekilde göster
function fetchSoldListRows() {
\$('#product-list-table-tbody').empty();
soldList.forEach(function(value, index) {
const rowHtml = generateProductRowHtml(value, { isAvoir: false });
\$('#product-list-table-tbody').append(rowHtml);
\$(`.product-select-row[data-uuid=\"\${value.uuid}\"]`).select2({
theme: 'bootstrap4',
dropdownCssClass: 'custom-select-dropdown',
placeholder: 'Ürün Seçin',
allowClear: true
});
applyMasks();
// Load measurement units for each product row with default unit
const product = products.find(p => p.productid === value.productid);
const defaultUnitId = product ? product.measurementUnitId : null;
const preferredUnitId = (value.selectedUnitId !== undefined && value.selectedUnitId !== null && value.selectedUnitId !== '') ? value.selectedUnitId : defaultUnitId;
loadMeasurementUnitsForRow(value.productid, value.uuid, preferredUnitId);
});
// Check for Gift Voucher amount and add AVOIR row using reusable component
const totalVoucherAmount = calculateTotalGiftVoucherAmount();
if (!totalVoucherAmount.isZero()) {
const avoirItem = {
uuid: 'avoir-row',
productid: null,
productName: 'AVOIR',
quantity: 1,
unAllocatedQuantity: 0,
unitPrice: totalVoucherAmount.times(-1).toNumber(), // Negative unit price
totalPurchasePrice: totalVoucherAmount.times(-1).toNumber(), // Negative total
discount: 0,
tax: 0
};
// We override the visual display of the price to be positive in the input if desired,
// OR we keep it negative to indicate deduction.
// The user request screenshot showed \"AVOIR\" in red.
// Standard AVOIR usually implies negative context but let's stick to the styling.
// IMPORTANT: The screenshot usually shows the total as negative, so unit price should likely be negative too.
// Wait, if Quantity is 1 and Total is -Amount, Unit Price must be -Amount.
const avoirRowHtml = generateProductRowHtml(avoirItem, { isAvoir: true });
\$('#product-list-table-tbody').append(avoirRowHtml);
}
fetchStocks();
updateProductSelect();
updateTotalAmounts();
\$('#warehouseselect').attr('disabled', soldList.length > 0);
}
// --- GIFT VOUCHER INTEGRATION ---
// Set default ID to 0 or a value that won't match if not found.
// Using Twig to find the ID.
const PAYMENT_METHOD_GIFT_VOUCHER_ID = (function() {
{% for pm in paymentMethods %}
{% if pm.name == 'Hediye Çeki' or pm.name == 'Gift Voucher' %}
return \"{{ pm.id }}\";
{% endif %}
{% endfor %}
return null;
})();
function calculateTotalGiftVoucherAmount() {
let totalVoucher = new Decimal(0);
if (!PAYMENT_METHOD_GIFT_VOUCHER_ID) return totalVoucher;
payments.forEach(function(p) {
if (String(p.paymentMethod) === String(PAYMENT_METHOD_GIFT_VOUCHER_ID)) {
totalVoucher = totalVoucher.plus(convertToDecimal(p.amount));
}
});
return totalVoucher;
}
// ---------------------------------
function renderServiceEditBtn(value){
const product = products.find(p => p.productid === value.productid);
if (isProductService(product.productid)) {
return `
<button type=\"button\"
class=\"btn btn-warning btn-sm edit-service-button\"
data-uuid=\"\${value.uuid}\"
title=\"Servisi Güncelle\">
<i class=\"fas fa-edit\"></i>
</button>
`;
}
return ''; // değilse hiç ekleme
}
function calculateRowFinancials(source) {
const quantity = convertToDecimal(source.quantity || 0);
const unAllocatedQuantity = convertToDecimal(source.unAllocatedQuantity || 0);
const unitPrice = convertToDecimal(source.unitPrice || 0);
const discountPercentage = convertToDecimal(source.discount || 0);
const taxPercentage = convertToDecimal(source.tax || 0);
const totalQuantity = quantity.plus(unAllocatedQuantity);
if (totalQuantity.isZero()) {
return {
subtotal: new Decimal(0),
discountAmount: new Decimal(0),
afterDiscount: new Decimal(0),
taxAmount: new Decimal(0),
total: new Decimal(0),
totalRounded: new Decimal(0),
taxPercentage
};
}
const subtotal = totalQuantity.mul(unitPrice);
const discountAmount = subtotal.mul(discountPercentage.div(100));
const afterDiscount = subtotal.minus(discountAmount);
const taxAmount = afterDiscount.mul(taxPercentage.div(100));
const total = afterDiscount.plus(taxAmount);
return {
subtotal,
discountAmount,
afterDiscount,
taxAmount,
total,
totalRounded: total.toDecimalPlaces(2, MONEY_ROUNDING_MODE),
taxPercentage
};
}
function aggregateSoldTotals() {
const aggregation = {
grossTotal: new Decimal(0),
totalDiscount: new Decimal(0),
subtotal: new Decimal(0),
tax: new Decimal(0),
grandTotal: new Decimal(0),
byTaxRate: {}
};
soldList.forEach(function(item) {
// item.totalPurchasePrice'ı kullan (manuel veya otomatik hesaplanmış)
const itemTotal = convertToDecimal(item.totalPurchasePrice || 0);
// Toplam üzerinden geriye doğru hesaplama
// Formül: total = subtotal × (1 - discount%) × (1 + tax%)
// Ters formül: afterDiscount = total / (1 + tax%)
// subtotal = afterDiscount / (1 - discount%)
const taxPercentage = convertToDecimal(item.tax || 0);
const discountPercentage = convertToDecimal(item.discount || 0);
const taxMultiplier = new Decimal(1).plus(taxPercentage.div(100));
// Adım 1: KDV'yi çıkar → İndirim sonrası tutarı bul
// afterDiscount = total / (1 + tax%)
const afterDiscount = itemTotal.div(taxMultiplier);
// Adım 2: KDV tutarını hesapla
// taxAmount = afterDiscount × tax%
const taxAmount = afterDiscount.mul(taxPercentage.div(100));
// NOT: \"Ara Toplam\" (subtotal) = afterDiscount (indirim uygulanmış tutar)
// Çünkü görüntülenen \"Ara Toplam\" indirim sonrası, KDV öncesi tutar
// Calculate Gross (before discount)
const quantity = convertToDecimal(item.quantity).plus(convertToDecimal(item.unAllocatedQuantity));
const unitPrice = convertToDecimal(item.unitPrice);
const grossLine = quantity.mul(unitPrice);
// Calculate Discount derived from Gross vs Net
const discountAmount = grossLine.minus(afterDiscount);
aggregation.grossTotal = aggregation.grossTotal.plus(grossLine);
aggregation.totalDiscount = aggregation.totalDiscount.plus(discountAmount);
aggregation.subtotal = aggregation.subtotal.plus(afterDiscount);
aggregation.tax = aggregation.tax.plus(taxAmount);
aggregation.grandTotal = aggregation.grandTotal.plus(itemTotal);
const rateKey = taxPercentage.toFixed(4);
if (!aggregation.byTaxRate[rateKey]) {
aggregation.byTaxRate[rateKey] = {
rate: taxPercentage,
amount: new Decimal(0)
};
}
aggregation.byTaxRate[rateKey].amount = aggregation.byTaxRate[rateKey].amount.plus(taxAmount);
});
aggregation.subtotalRounded = aggregation.subtotal.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
aggregation.taxRounded = aggregation.tax.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
aggregation.grandTotalRounded = aggregation.grandTotal.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
return aggregation;
}
// Track which row is being updated to prevent circular updates
let rowUpdatingFromTotal = {};
let rowUpdatingFromUnitPrice = {};
// Track rows where total was manually entered (should not recalculate total)
let rowHasManualTotal = {};
function updateSoldProductFromRow(uuid) {
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex === -1) {
return;
}
// Eğer bu satır toplam tarafından güncelleniyorsa, tekrar toplam güncelleme
if(rowUpdatingFromTotal[uuid]) {
return;
}
rowUpdatingFromUnitPrice[uuid] = true;
const \$row = \$(`#\${uuid}`);
const quantity = convertToDecimal(\$row.find('.quantity-input-row').val()).toNumber();
const unitPrice = convertToDecimal(\$row.find('.price-input-row').val()).toNumber();
const discount = convertToDecimal(\$row.find('.discount-select-row').val()).toNumber();
const tax = convertToDecimal(\$row.find('.tax-select-row').val()).toNumber();
soldList[itemIndex].quantity = quantity;
soldList[itemIndex].unitPrice = unitPrice;
soldList[itemIndex].discount = discount;
soldList[itemIndex].tax = tax;
const selectedUnitId = \$row.find('.quantity-unit-select').val();
computeBaseUnitQuantity(soldList[itemIndex].productid, selectedUnitId, quantity, function(baseQty){
soldList[itemIndex].baseUnitQuantity = convertToDecimal(baseQty || quantity).toNumber();
});
// Eğer bu satırda manuel toplam girildiyse, toplam sabit kalmalı, sadece birim fiyat güncellensin
if (rowHasManualTotal[uuid]) {
// Manuel toplam korunuyor, birim fiyatı tersine hesapla
const manualTotal = convertToDecimal(soldList[itemIndex].totalPurchasePrice);
const totalQuantity = convertToDecimal(quantity).plus(convertToDecimal(soldList[itemIndex].unAllocatedQuantity || 0));
if (!totalQuantity.isZero()) {
const discountMultiplier = new Decimal(1).minus(convertToDecimal(discount).div(100));
const taxMultiplier = new Decimal(1).plus(convertToDecimal(tax).div(100));
if (discountMultiplier.isZero()) {
soldList[itemIndex].unitPrice = 0;
\$row.find('.price-input-row').val('0.00');
} else if (!taxMultiplier.isZero()) {
const calculatedUnitPrice = manualTotal
.div(taxMultiplier)
.div(discountMultiplier)
.div(totalQuantity)
.toDecimalPlaces(6, MONEY_ROUNDING_MODE);
soldList[itemIndex].unitPrice = calculatedUnitPrice.toNumber();
\$row.find('.price-input-row').val(calculatedUnitPrice.toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
}
}
// Toplam input'u DEĞİŞTİRME - manuel değer korunuyor
} else {
// Normal hesaplama - toplam otomatik hesaplansın
const rowTotals = calculateRowFinancials(soldList[itemIndex]);
soldList[itemIndex].totalPurchasePrice = rowTotals.totalRounded.toNumber();
\$row.find('.total-input-row').val(rowTotals.totalRounded.toFixed(2));
}
updateTotalAmounts();
fetchStocks();
fetchPaymentForm();
setTimeout(function(){ delete rowUpdatingFromUnitPrice[uuid]; }, 0);
}
// Toplam tutar değiştirildiğinde birim fiyatı tersine hesaplar
function updateUnitPriceFromTotal(uuid) {
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex === -1) {
return;
}
// Eğer bu satır birim fiyat tarafından güncelleniyorsa, döngüyü engelle
if(rowUpdatingFromUnitPrice[uuid]) {
return;
}
rowUpdatingFromTotal[uuid] = true;
// ÖNEMLİ: Toplam manuel girildi, bu satırı işaretle
rowHasManualTotal[uuid] = true;
const \$row = \$(`#\${uuid}`);
const totalValue = convertToDecimal(\$row.find('.total-input-row').val());
const item = soldList[itemIndex];
const totalQuantity = convertToDecimal(item.quantity || 0).plus(convertToDecimal(item.unAllocatedQuantity || 0));
if (totalQuantity.isZero()) {
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
return;
}
// Manuel girilen toplam değerini kaydet
soldList[itemIndex].totalPurchasePrice = totalValue.toNumber();
// Eğer toplam 0 ise, birim fiyat da 0 olmalı
if (totalValue.isZero()) {
\$row.find('.price-input-row').val('0.00');
soldList[itemIndex].unitPrice = 0;
updateTotalAmounts();
fetchStocks();
fetchPaymentForm();
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
return;
}
const discountMultiplier = new Decimal(1).minus(convertToDecimal(item.discount || 0).div(100));
const taxMultiplier = new Decimal(1).plus(convertToDecimal(item.tax || 0).div(100));
// %100 indirimde discountMultiplier = 0 olur, bu durumda birim fiyat 0 olmalı
if (discountMultiplier.isZero()) {
\$row.find('.price-input-row').val('0.00');
soldList[itemIndex].unitPrice = 0;
updateTotalAmounts();
fetchStocks();
fetchPaymentForm();
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
return;
}
if (taxMultiplier.isZero()) {
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
return;
}
// Formül: unitPrice = total / ((1 + tax%) * (1 - discount%) * quantity)
const unitPrice = totalValue
.div(taxMultiplier)
.div(discountMultiplier)
.div(totalQuantity)
.toDecimalPlaces(6, MONEY_ROUNDING_MODE);
\$row.find('.price-input-row').val(unitPrice.toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
soldList[itemIndex].unitPrice = unitPrice.toNumber();
// Sadece stok ve genel toplamları güncelle, satır toplamını değiştirme
updateTotalAmounts();
fetchStocks();
fetchPaymentForm();
setTimeout(function(){ delete rowUpdatingFromTotal[uuid]; }, 0);
}
// =============================================================================
// WAREHOUSE SELECTION EVENT HANDLER
// =============================================================================
// Handle warehouse selection change
\$('#warehouseselect').on('change', function() {
getProductsFromWarehouse();
});
// Auto-select warehouse if there is only one
var \$warehouseSelect = \$('#warehouseselect');
if (\$warehouseSelect.find('option').length === 2 && \$warehouseSelect.val() === \"\") {
var firstWarehouseVal = \$warehouseSelect.find('option').eq(1).val();
\$warehouseSelect.val(firstWarehouseVal).trigger('change');
}
// Load measurement units for a specific product row (with caching)
let measurementUnitsCache = {};
function loadMeasurementUnitsForRow(productId, uuid, defaultUnitId = null) {
// Check cache first
if (measurementUnitsCache[productId]) {
populateUnitSelectForRow(uuid, measurementUnitsCache[productId], defaultUnitId);
return;
}
// Load from server if not cached
\$.get('/product/' + productId + '/measurement-units', function(units) {
// Cache the units
measurementUnitsCache[productId] = units || [];
populateUnitSelectForRow(uuid, units, defaultUnitId);
}).fail(function() {
console.log('Failed to load measurement units for product:', productId);
measurementUnitsCache[productId] = []; // Cache empty array to prevent repeated requests
});
}
// Populate unit select dropdown for a specific row
function populateUnitSelectForRow(uuid, units, defaultUnitId = null) {
const \$select = \$(`.quantity-unit-select[data-uuid=\"\${uuid}\"]`);
\$select.empty();
if (Array.isArray(units) && units.length > 0) {
units.forEach(function(unit) {
const isSelected = defaultUnitId !== null && defaultUnitId !== undefined && defaultUnitId !== '' && String(unit.id) === String(defaultUnitId);
const selectedAttr = isSelected ? ' selected' : '';
\$select.append('<option value=\"' + unit.id + '\"' + selectedAttr + '>' + unit.symbol + '</option>');
});
if (units.length === 1) {
\$select.prop('disabled', true);
} else {
\$select.prop('disabled', false);
}
} else {
\$select.append('<option value=\"\">-</option>');
\$select.prop('disabled', false);
}
}
// Update total amounts in footer
function updateTotalAmounts() {
const taxLabelPrefix = \"TVA\";
const taxTotalLabel = \"{{ 'total'|trans|e('js') }}\";
const formatTaxRate = function(rateDecimal) {
const rateNumber = convertToDecimal(rateDecimal || 0).toNumber();
if (!isFinite(rateNumber)) {
return \"0\";
}
if (Math.abs(rateNumber % 1) < 1e-6) {
return rateNumber.toFixed(0);
}
return rateNumber.toFixed(2).replace(/\\.?0+\$/, \"\");
};
const totals = aggregateSoldTotals();
const \$tfoot = \$(\"#product-list-table tfoot\");
\$tfoot.empty();
// Helper to create row
const addRow = (label, value, isBold=false, colorClass='') => {
const row = \$(`<tr class=\"summary-row \${isBold ? 'font-weight-bold' : ''} \${colorClass}\">
<td colspan=\"8\">
<div class=\"sale-summary-row\">
<span class=\"summary-label\">\${label}</span>
<span class=\"summary-value\">\${formatCurrency(value)}</span>
</div>
</td>
</tr>`);
\$tfoot.append(row);
};
// 1. Total H.T. (Gross)
addRow('Total H.T.:', totals.grossTotal, true);
// 2. Remise
if (!totals.totalDiscount.isZero() && totals.totalDiscount.toNumber() > 0.005) {
const \$row = \$(`<tr class=\"summary-row\">
<td colspan=\"8\">
<div class=\"sale-summary-row\">
<span class=\"summary-label\">Remise:</span>
<span class=\"summary-value\">\${formatCurrency(totals.totalDiscount.negated())}</span>
</div>
</td>
</tr>`);
\$tfoot.append(\$row);
}
// 3. Total H.T. (Net)
addRow('Total H.T.:', totals.subtotal, true);
// 4. AVOIR
const totalVoucherAmount = calculateTotalGiftVoucherAmount();
let finalTotal = totals.grandTotalRounded; // Assuming totals.grandTotalRounded is available in result? Yes
// Wait, aggregateSoldTotals returns grandTotalRounded.
// But calculateSaleSummary in edit.html.twig returned summary.roundedTotal.
if (!totalVoucherAmount.isZero()) {
const \$avoirRow = \$(`<tr class=\"summary-row text-danger font-weight-bold\">
<td colspan=\"8\">
<div class=\"sale-summary-row\">
<span class=\"summary-label\">AVOIR:</span>
<span class=\"summary-value\">-\${formatCurrency(totalVoucherAmount)}</span>
</div>
</td>
</tr>`);
\$tfoot.append(\$avoirRow);
finalTotal = finalTotal.minus(totalVoucherAmount);
}
// 5. TVA
const taxEntries = Object.values(totals.byTaxRate)
.filter(function(entry) { return !entry.amount.isZero(); })
.sort(function(a, b) { return a.rate.toNumber() - b.rate.toNumber(); });
if (taxEntries.length) {
taxEntries.forEach(function(entry) {
addRow(taxLabelPrefix + \" (\" + formatTaxRate(entry.rate) + \"%):\", entry.amount);
});
} else {
addRow(taxLabelPrefix + \" (0%):\", 0);
}
// 6. TOTAL
const \$totalRow = \$(`<tr class=\"total-row summary-row font-weight-bold\">
<td colspan=\"8\">
<div class=\"sale-summary-row summary-total\">
<span class=\"summary-label\">TOTAL À PAYER (EUR):</span>
<span class=\"summary-value display-5\">\${formatCurrency(finalTotal)}</span>
</div>
</td>
</tr>`);
\$tfoot.append(\$totalRow);
calculateAndWriteDiffBetweenPaymentAndSolds();
}
// Update sold list item by UUID
function updateSoldListItem(uuid, field, value) {
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex === -1) {
return;
}
soldList[itemIndex][field] = convertToDecimal(value).toNumber();
updateSoldProductFromRow(uuid);
}
// Satıştaki miktarları listeden düşme Un Allocated HARİÇ tutulur
function fetchStocks() {
// soldList'i productid'e göre grupla
const groupedSoldList = soldList.reduce((acc, soldProduct) => {
if (!acc[soldProduct.productid]) {
acc[soldProduct.productid] = 0;
}
acc[soldProduct.productid] += convertToDecimal(soldProduct.baseUnitQuantity).toNumber();
return acc;
}, {});
// products listesindeki totalQuantity'yi güncelle
products.forEach(product => {
if (groupedSoldList[product.productid]) {
const totalSoldQuantity = groupedSoldList[product.productid];
product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity - totalSoldQuantity).toNumber());
} else {
// Eğer soldList'te bu ürün yoksa, quantity aynı kalır
product.quantity = roundStockToDecimal(convertToDecimal(product.totalQuantity).toNumber());
}
});
calculateAndWriteDiffBetweenPaymentAndSolds();
}
function getTotalSoldAmount() {
return aggregateSoldTotals().grandTotalRounded.toNumber();
}
\$(document).on('blur', '#payment-list-table-tbody .payment-amount-input', function() {
// Buraya kod eklenebilir
});
// Gelen değeri decimale çevirme
function convertToDecimal(value) {
try {
if (value instanceof Decimal) {
return value;
}
if (value === undefined || value === null) {
return new Decimal(0);
}
if (typeof value === 'number') {
if (!isFinite(value) || isNaN(value)) {
return new Decimal(0);
}
return new Decimal(value);
}
if (typeof value === 'string') {
// Keep digits, minus, dot and comma; strip others
var sanitized = value.replace(/[^0-9.,-]/g, '').replace(/,/g, '').trim();
if (sanitized === '' || sanitized === '-' || sanitized === '.' || sanitized === '-.') {
return new Decimal(0);
}
return new Decimal(sanitized);
}
return new Decimal(0);
} catch (e) {
return new Decimal(0);
}
}
// Seçilen ürünün birimini dropdown'da gösterme (handled by main product select handler)
function clearUnitSelects(){
\$('#adder-quantity-unit-select').empty().append('<option value=\"\">-</option>');
\$('#adder-unallocated-quantity-unit-select').empty().append('<option value=\"\">-</option>');
\$('#modal-unallocated-unit').empty().append('<option value=\"\">-</option>');
}
// Select içine çevrilebilir birimleri koyuyor
function populateSelectWithUnits(\$select, units) {
\$select.empty();
if (Array.isArray(units) && units.length > 0) {
units.forEach(function(unit) {
\$select.append('<option value=\"' + unit.id + '\">' + unit.symbol + '</option>');
});
if (units.length === 1) {
\$select.prop('disabled', true);
} else {
\$select.prop('disabled', false);
}
} else {
\$select.append('<option value=\"\">-</option>');
\$select.prop('disabled', false);
}
}
// Ürünün çevrilebilir birimlerini select içine koyuyoruz.
function refreshUnitSelectsForProduct(product) {
if (!product || !product.convertibleUnits) {
clearUnitSelects();
return;
}
populateSelectWithUnits(\$('#adder-quantity-unit-select'), product.convertibleUnits);
populateSelectWithUnits(\$('#adder-unallocated-quantity-unit-select'), product.convertibleUnits);
populateSelectWithUnits(\$('#modal-unallocated-unit'), product.convertibleUnits);
}
// TODO: Birimler doldurulurken ilgili birimin karşılığındaki miktar yazılacak
function fillUnitSelects(product){
// Check cache first
if (measurementUnitsCache[product.productid]) {
product.convertibleUnits = measurementUnitsCache[product.productid];
refreshUnitSelectsForProduct(product);
computeAndShowBaseUnitEquivalent();
return;
}
\$.get('/product/' + product.productid + '/measurement-units', function(units) {
// Cache the units
measurementUnitsCache[product.productid] = units || [];
// Store in product object
product.convertibleUnits = units || [];
// Update selects
refreshUnitSelectsForProduct(product);
// Set default unit as selected
if (product.measurementUnitId) {
\$('#adder-quantity-unit-select').val(product.measurementUnitId);
\$('#adder-unallocated-quantity-unit-select').val(product.measurementUnitId);
\$('#modal-unallocated-unit').val(product.measurementUnitId);
}
computeAndShowBaseUnitEquivalent();
});
}
// 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
function computeAndShowBaseUnitEquivalent() {
try {
const selectedProduct = getSelectedProduct();
if (!selectedProduct || !selectedProduct.productid) {
updateAllocatedQuantityHint('');
return;
}
const fromUnitId = \$('#adder-quantity-unit-select').val();
const qtyStr = \$('#adder-allocated-quantity').val();
// If base unit is selected, do not show duplicate label
if (fromUnitId && String(fromUnitId) === String(selectedProduct.measurementUnitId)) {
updateAllocatedQuantityHint('');
return;
}
if (!fromUnitId || fromUnitId === '' || qtyStr === '' || qtyStr === null) {
updateAllocatedQuantityHint('');
return;
}
const quantity = convertToDecimal(qtyStr).toNumber();
if (isNaN(quantity)) {
updateAllocatedQuantityHint('');
return;
}
\$.ajax({
url: '/admin/measurement/convert-to-product-unit',
method: 'POST',
dataType: 'json',
data: {
productId: selectedProduct.productid,
fromUnitId: fromUnitId,
quantity: quantity
},
success: function(resp) {
if (resp && resp.success) {
const unitText = selectedProduct.measurementUnit ? (' ' + selectedProduct.measurementUnit) : '';
updateAllocatedQuantityHint((Number(resp.result).toFixed(2)) + unitText);
} else {
updateAllocatedQuantityHint('');
}
},
error: function() {
updateAllocatedQuantityHint('');
}
});
} catch (e) {
updateAllocatedQuantityHint('');
}
}
// Base unit tipinden miktar.
function updateAllocatedQuantityHint(text) {
const \$group = \$('#adder-allocated-quantity').closest('.input-group');
if (\$('#allocated-quantity-converted-hint').length === 0) {
\$('<small id=\"allocated-quantity-converted-hint\" class=\"form-text text-muted\"></small>').insertAfter('#adder-quantity-unit-select');
}
\$('#allocated-quantity-converted-hint').text(text || '');
}
function roundToDecimal(value, decimalPlaces = 2) {
const decimalValue = new Decimal(value);
const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_UP);
return roundedValue.toNumber();
}
function roundStockToDecimal(value, decimalPlaces = 2) {
const decimalValue = new Decimal(value);
const roundedValue = decimalValue.toDecimalPlaces(decimalPlaces, Decimal.ROUND_DOWN);
return roundedValue.toNumber();
}
function selectProduct(productId) {
products.forEach(product => product.selected = false);
let product = products.find(product => product.productid === productId);
if (product) {
product.selected = true;
}
return products;
}
function getSelectedProduct() {
return products.find(product => product.selected === true);
}
// Ürün seçme selectini yeniden güncelleme.
function updateProductSelect() {
\$('#productselect').empty();
\$('#productselect').append('<option value=\"0\" data-image=\"image-not-found.webp\">Ürün Seçin</option>');
\$.each(products, function(index, value) {
var image = value.image;
var option = '<option value=\"' + value.id + '\" data-image=\"' + image + '\" class=\"stockoption\">' + value.name + ' - ' + value.code + ' - ' + value.measurement + ' - ' + value.quantity + '</option>';
\$('#productselect').append(option);
});
\$('#productselect').attr('disabled', false);
}
\$('#products_sold_quantity').mask(\"##0.00\", {
reverse: true
});
\$('#adder-allocated-quantity').mask(\"##0.00\", {
reverse: true
});
\$('#adder-unallocated-quantity').mask(\"##0.00\", {
reverse: true
});
\$('#adder-price').mask(\"##0.00\", {
reverse: true
});
\$('#adder-total').mask(\"##0.00\", {
reverse: true
});
\$('#products_sold_unitPriceFob').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_unitPriceNavlun').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_totalUnitPrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_totalUnitPurchasePrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_totalPrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#products_sold_totalPuchasePrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#sales_form_totalPurchasePrice').mask(\"#,##0.00\", {
reverse: true
});
\$('#sales_form_totalPurchasePrice').attr('readonly', 'readonly');
var totalQuantity = 0;
var productId = 0;
var productname = \$('#products_sold_productName');
var productmeasurement = \$('#products_sold_measurement');
var unitPriceFob = \$('#products_sold_unitPriceFob');
var unitPricenavlun = \$('#products_sold_unitPriceNavlun');
var totalUnitPrice = \$('#products_sold_totalUnitPrice');
// ÖDEME FORMU LİSTEYE EKLEME
\$('#save-payment-btn').on('click', function() {
let amount = roundToDecimal(convertToDecimal(\$('#payment_amount').val()));
let payment_paymentStatus = \$('#payment_paymentStatus').val();
let payment_paymentDate = \$('#payment_paymentDate').val();
let payment_dueDate = \$('#payment_dueDate').val();
let payment_paymentMethod = \$('#payment_paymentMethod').val();
let payment_description = \$('#payment_description').val();
if (amount <= 0 || !payment_paymentStatus || !payment_paymentDate || !payment_dueDate || !payment_paymentMethod || !payment_description) {
throw new Error(\"Formdaki eksikleri tamamlayın lütfen\")
}
payment = {
uuid: crypto.randomUUID(),
amount: amount,
status: payment_paymentStatus,
paymentDate: payment_paymentDate,
paymentDueDate: payment_dueDate,
paymentMethod: payment_paymentMethod,
description: payment_description,
voucherCode: \$('#payment_voucherCode').val()
}
payments.push(payment);
fetchPaymentForm();
// Trigger update to show AVOIR if needed
fetchSoldListRows();
});
// Ödeme satırını silme işlemi
\$(document).on('click', '.delete-payment', function() {
\$row = \$(this).closest('tr');
let id = \$row.attr(\"id\");
payments = payments.filter(payment => payment.uuid !== id);
fetchPaymentForm();
// Trigger update to remove AVOIR if needed
fetchSoldListRows();
});
// Ödeme formunu güncelleme
function fetchPaymentForm() {
\$('#payment-list-table-tbody').empty();
payments.forEach(function(val) {
var newRow = `<tr id=\"\${val.uuid}\">
<td><input type=\"text\" class=\"form-control payment-amount payment-amount-input\" value=\"\${addCommas(val.amount) || ''}\" name=\"paymentAmount[]\" placeholder=\"Miktar\"></td>
<td>
<select class=\"form-control\" name=\"paymentStatus[]\">
<option value=\"0\" \${val.status === \"0\" ? 'selected' : ''}>{% trans %} payment_status.paid {% endtrans %}</option>
<option value=\"1\" \${val.status === \"1\" ? 'selected' : ''}>{% trans %} payment_status.not_paid {% endtrans %}</option>
<option value=\"2\" \${val.status === \"2\" ? 'selected' : ''}>{% trans %} payment_status.term_sale {% endtrans %}</option>
</select>
</td>
<td><input name=\"paymentDate[]\" type=\"date\" class=\"form-control\" value=\"\${val.paymentDate || ''}\" required></td>
<td><input name=\"paymentDueDate[]\" type=\"date\" class=\"form-control\" value=\"\${val.paymentDueDate || ''}\" required></td>
<td>
<select class=\"form-control\" name=\"paymentMethod[]\">
<option value=\"0\">Ödeme Yöntemi Seçin</option>
{% for paymentMethod in paymentMethods %}
<option value=\"{{ paymentMethod.id }}\" \${val.paymentMethod === '{{ paymentMethod.id }}' ? 'selected' : ''}>
{{ paymentMethod.name }}
</option>
{% endfor %}
</select>
</td>
<td><input type=\"text\" class=\"form-control\" value=\"\${val.description || ''}\" name=\"description[]\">
<input type=\"hidden\" name=\"voucherCode[]\" value=\"\${val.voucherCode || ''}\">
</td>
<td><button type=\"button\" class=\"btn btn-primary btn-sm save-payment-row\">{% trans %} save {% endtrans %}</button></td>
<td><button type=\"button\" class=\"btn btn-danger btn-sm delete-payment\">{% trans %} delete {% endtrans %}</button></td>
</tr>`;
// Yeni satırı tabloya ekle
\$('#payment-list-table-tbody').append(newRow);
\$('#add-payment-modal').modal(\"hide\");
resetPaymentForm();
});
calculateAndWriteDiffBetweenPaymentAndSolds();
}
function updatePaymentByUUID(uuid, updatedData) {
const paymentIndex = payments.findIndex(payment => payment.uuid === uuid);
if (paymentIndex === -1) {
console.error(`Payment with UUID \${uuid} not found!`);
return false;
}
payments[paymentIndex] = {
...payments[paymentIndex], // Mevcut veriler
...updatedData // Yeni veriler
};
fetchPaymentForm();
return true;
}
function calculateAndWriteDiffBetweenPaymentAndSolds(existingTotals = null) {
const totals = existingTotals || aggregateSoldTotals();
const saleTotalRounded = totals.grandTotalRounded;
const paymentTotal = convertToDecimal(calculateTotalPaymentAmount());
const diff = totals.grandTotal.minus(paymentTotal);
const diffRounded = diff.toDecimalPlaces(2, MONEY_ROUNDING_MODE);
\$('#total-amount-span').html(addCommas(saleTotalRounded.toFixed(2)));
\$('#remainder-amount-span').html(addCommas(diffRounded.toFixed(2)));
}
function resetPaymentForm() {
\$('#payment_amount').val('');
\$('#payment_description').val('');
\$('#payment_voucherCode').val('');
\$('#payment_voucherCode').closest('.form-group').hide(); // Reset visibility
}
\$(document).on('click', '.save-payment-row', function() {
const \$row = \$(this).closest('tr');
const uuid = \$row.attr('id');
const updatedData = {
amount: convertToDecimal(\$row.find('input[name=\"paymentAmount[]\"]').val().replace(',', '')).toNumber(),
status: \$row.find('select[name=\"paymentStatus[]\"]').val(),
paymentDate: \$row.find('input[name=\"paymentDate[]\"]').val(),
paymentDueDate: \$row.find('input[name=\"paymentDueDate[]\"]').val(),
paymentMethod: \$row.find('select[name=\"paymentMethod[]\"]').val(),
description: \$row.find('input[name=\"description[]\"]').val(),
voucherCode: \$row.find('input[name=\"voucherCode[]\"]').val()
};
const result = updatePaymentByUUID(uuid, updatedData);
if (result) {
showSuccessAlert(\"Ödeme satırı güncellendi :\", updatedData);
} else {
console.log(\"Failed to update payment.\");
}
});
function resetAddProductForm() {
\$('#products_sold_quantity').val(0);
\$('#products_sold_totalUnitPurchasePrice').val(0);
\$('#products_sold_unAllocatedQuantity').val(0);
\$('#products_sold_totalPuchasePrice').val(0);
}
function calculatePrice() {
var quantityP = convertToFloat(\$('#products_sold_quantity').val());
var unAllocatedQuantityP = convertToFloat(\$('#products_sold_unAllocatedQuantity').val());
var totalUnitPurchasePriceP = convertToFloat(\$('#products_sold_totalUnitPurchasePrice').val());
var taxP = convertToFloat(\$('#products_sold_tax').val());
var discountP = convertToFloat(\$('#products_sold_discount').val());
var totalAmount = (quantityP + unAllocatedQuantityP) * totalUnitPurchasePriceP;
var discountedAmount = totalAmount - (totalAmount * (discountP / 100));
var finalAmount = discountedAmount + (discountedAmount * (taxP / 100));
finalAmount = roundToTwoDecimals(finalAmount);
\$('#products_sold_totalPuchasePrice').val(finalAmount);
}
\$(document).on('change', '#products_sold_tax', calculatePrice);
\$(document).on('change', '#products_sold_discount', calculatePrice);
\$(document).on('change', '#products_sold_quantity', calculatePrice);
\$(document).on('change', '#products_sold_totalUnitPurchasePrice', calculatePrice);
function roundToTwoDecimals(number) {
var factor = Math.pow(10, 2);
return (Math.round(number * factor) / factor).toFixed(2);
}
function validate(s) {
var rgx = /^[0-9]*\\.?[0-9]*\$/;
return s.match(rgx);
}
function convertToFloat(value) {
if (typeof value === \"string\") {
value = value.replace(',', '');
}
return parseFloat(value);
}
\$(document).on('change', 'input[name=\"totalUnitPurchasePrice[]\"]', function() {
var unitPrice = \$(this).val();
});
var productCode = 0;
\$('#productselect').on('change', function() {
\$.ajax({
url: \"/stock-detail/\" + \$('#productselect option:selected').val(),
dataType: 'JSON',
method: 'GET',
success: function(response) {
var productid = response.productid;
var product = products.find(function(item) {
return item.productid === productid;
});
product.length = response.length;
product.priceFob = response.priceFob;
product.priceNavlun = response.priceNavlun;
product.totalQuantity = response.totalQuantity;
product.quantity = response.quantity;
product.unAllocatedQuantity = response.unAllocatedQuantity;
product.thickness = response.thickness;
product.totalUnitPrice = response.totalUnitPrice;
product.width = response.width;
selectProduct(product.productid);
getProductCost(product.productid);
fetchStocks();
productname.val(product.name);
productmeasurement.val(product.measurement);
productCode = product.code;
unitPriceFob.val(addCommas(product.priceFob));
unitPricenavlun.val(addCommas(product.priceNavlun));
totalUnitPrice.val(addCommas(product.priceNavlun + response.priceFob));
totalQuantity = product.totalQuantity;
productId = product.productid;
// Seçilen ürün için birimleri yükle ve ana birimi seç
fillUnitSelects(product);
computeAndShowBaseUnitEquivalent();
},
error: function(error) {
// Hata yönetimi
}
});
});
function isProductService(productId){
const product = products.find(product => product.productid === productId);
const productType = product.productType.key;
if(productType === \"INSTALLATION_SERVICE\" || productType === \"MAINTENANCE_SERVICE\" || productType === \"CONSULTATION_SERVICE\" || productType === 'TREE'){
return true;
}
return false;
}
// TODO
\$('#add-product-row-btn').on('click', function() {
var productId = \$('#productselect').val();
if (!productId || productId <= 0) {
showErrorAlert(\"Ürün seçimi yapın\");
return;
}
let product = getSelectedProduct();
const productType = product.productType.key;
var quantity = \$('#adder-allocated-quantity').val();
var unAllocatedQuantity = \$('#adder-unallocated-quantity').val();
var unitPrice = \$('#adder-price').val();
var tax = \$('#adder-tva-select').val();
var discount = \$('#adder-discount-select').val();
var totalPurchasePrice = \$('#adder-total').val();
quantity = convertToDecimal(quantity);
unAllocatedQuantity = convertToDecimal(unAllocatedQuantity);
unitPrice = convertToDecimal(unitPrice);
tax = convertToDecimal(tax);
discount = convertToDecimal(discount);
totalPurchasePrice = convertToDecimal(totalPurchasePrice);
if(productType === 'TREE') {
}
checkStockQuantity(quantity);
const selectedUnitId = \$('#adder-quantity-unit-select').val();
if (!selectedUnitId || String(selectedUnitId) === '') {
showErrorAlert('Lütfen birim seçiniz.');
return;
}
const item = addProductToSoldList(
product.productid,
product.name,
product.code,
product.measurement,
quantity,
unitPrice,
tax,
discount,
totalPurchasePrice,
product.warehouseid,
unAllocatedQuantity,
product.thickness,
product.width,
product.length,
product.cost,
product.productType,
selectedUnitId,
);
if(isProductService(product.productid)){
\$('#update_service_id').val(item.uuid);
\$('#updateServiceModal').modal('show');
}else{
if(quantity.toNumber() === 0 && unAllocatedQuantity.toNumber() === 0) {
showErrorAlert(\"Miktar veya tahsis edilmemiş miktar 0 olamaz\");
return;
}
}
fetchSoldListRows();
resetAdderRow();
});
\$('#service_modal_save_changes_btn').on('click', function() {
const uuid = \$('#update_service_id').val();
const soldProduct = soldList.find(soldProduct => soldProduct.uuid === uuid);
const product = products.find(product => product.productid === soldProduct.productid);
const productType = product.productType.key;
// Modal değerlerini geri alıp objeye yazıyoruz
soldProduct.productName = \$('#update_service_name').val();
soldProduct.cost = parseFloat(\$('#update_service_cost').val());
soldProduct.description = \$('#update_service_description').val();
const tr = \$('#' + uuid);
console.log(\"Servis güncellendi:\", soldProduct);
\$('#updateServiceModal').modal('hide');
// Rows'ları yeniden render et ki service adı dropdown'da görünsün
fetchSoldListRows();
updateTotalAmounts();
if(isProductService(product.productid)){
addServiceEditButtonIfNeeded(uuid);
}
});
\$(document).on('click', '.edit-service-button', function () {
const uuid = \$(this).data('uuid');
const soldProduct = soldList.find(sp => sp.uuid === uuid);
\$('#update_service_id').val(uuid);
\$('#update_service_name').val(soldProduct?.productName || \"\");
\$('#update_service_cost').val(soldProduct?.cost || \"0.00\");
\$('#update_service_description').val(soldProduct?.description || \"\");
\$('#updateServiceModal').modal('show');
});
// TODO:
function addServiceEditButtonIfNeeded(uuid) {
const soldProduct = soldList.find(sp => sp.uuid === uuid);
const product = products.find(p => p.productid === soldProduct.productid);
const productType = product.productType.key;
// Sadece ürün service ise buton ekle
if (product && isProductService(product.productid)) {
const tr = \$('#' + uuid);
// Eğer daha önce eklenmediyse ekle
if (tr.find('.edit-service-button').length === 0) {
const btn = `
<button type=\"button\"
class=\"btn btn-warning btn-sm edit-service-button\"
data-uuid=\"\${uuid}\"
title=\"Servisi Güncelle\">
<i class=\"fas fa-edit\"></i>
</button>`;
// örnek: sil butonunun yanına ekleyelim
tr.find('.btn-group').prepend(btn);
}
}
}
// Reset adder row form after adding product
function resetAdderRow() {
\$('#productselect').val('').trigger('change');
\$('#adder-allocated-quantity').val('0');
\$('#adder-unallocated-quantity').val('0');
\$('#adder-price').val('0');
\$('#adder-discount-select').val('0');
\$('#adder-tva-select').val('0');
\$('#adder-total').val('0');
\$('#unalloc-display').text('0');
clearUnitSelects();
\$('#info-details-content').hide();
\$('#show-info-btn i').removeClass('fa-chevron-up').addClass('fa-info-circle');
}
// =============================================================================
// EVENT HANDLERS FOR SALES LIST EDITING
// =============================================================================
// Show/hide product info details
\$(document).on('click', '.show-row-info-btn', function() {
const \$infoContent = \$(this).closest('tr').next('.info-details-row-class').find('.info-details-content-class');
const \$icon = \$(this).find('i');
if (\$infoContent.is(':visible')) {
\$infoContent.hide();
\$icon.removeClass('fa-chevron-up').addClass('fa-info-circle');
\$(this).closest('tr').next('.info-details-row-class').hide();
} else {
\$infoContent.show();
\$icon.removeClass('fa-info-circle').addClass('fa-chevron-up');
\$(this).closest('tr').next('.info-details-row-class').show();
}
});
\$(document).on('change', '.product-select-row', function() {
const uuid = \$(this).data('uuid');
const newProductId = parseInt(\$(this).val());
const soldItemIndex = soldList.findIndex(item => item.uuid === uuid);
if (soldItemIndex > -1) {
const newProduct = products.find(p => p.productid === newProductId);
if (newProduct) {
soldList[soldItemIndex].productid = newProduct.productid;
soldList[soldItemIndex].productName = newProduct.name;
soldList[soldItemIndex].code = newProduct.code;
soldList[soldItemIndex].measurement = newProduct.measurement;
soldList[soldItemIndex].cost = newProduct.cost;
}
fetchSoldListRows();
updateTotalAmounts();
}
console.table(soldList);
});
// Handle unit price changes in sold list rows
\$(document).on('input change', '.price-input-row', function() {
const uuid = \$(this).data('uuid');
const value = \$(this).val();
// Birim fiyat manuel değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
updateSoldListItem(uuid, 'unitPrice', value);
});
// Handle discount changes in sold list rows
\$(document).on('change', '.discount-select-row', function() {
const uuid = \$(this).data('uuid');
const value = \$(this).val();
// İndirim değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
updateSoldListItem(uuid, 'discount', value);
});
// Handle tax changes in sold list rows
\$(document).on('change', '.tax-select-row', function() {
const uuid = \$(this).data('uuid');
const value = \$(this).val();
// KDV değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
updateSoldListItem(uuid, 'tax', value);
});
// Handle unit selection changes in sold list rows
\$(document).on('change', '.quantity-unit-select', function() {
const uuid = \$(this).data('uuid');
const unitId = \$(this).val();
const \$quantityInput = \$(`.quantity-input-row[data-uuid=\"\${uuid}\"]`);
const currentQuantity = parseFloat(\$quantityInput.val()) || 0;
// Update the selected unit in sold list
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex !== -1) {
const item = soldList[itemIndex];
const oldUnitId = item.selectedUnitId;
item.selectedUnitId = unitId;
// Show unit conversion equivalent
showUnitConversionForRow(uuid, item.productid, currentQuantity, unitId);
// If changing from one unit to another, convert the quantity
if (oldUnitId && oldUnitId !== unitId && unitId && currentQuantity > 0) {
convertQuantityBetweenUnits(item.productid, currentQuantity, oldUnitId, unitId, function(convertedQuantity) {
if (convertedQuantity !== null) {
\$quantityInput.val(convertedQuantity.toFixed(2));
updateSoldListItem(uuid, 'quantity', convertedQuantity);
// Update conversion display with new quantity
showUnitConversionForRow(uuid, item.productid, convertedQuantity, unitId);
}
});
}
// Recompute and store base unit quantity for the row
computeBaseUnitQuantity(item.productid, unitId, parseFloat(\$quantityInput.val()) || 0, function(baseQty){
const idx = soldList.findIndex(sp => sp.uuid === uuid);
if(idx !== -1){ soldList[idx].baseUnitQuantity = convertToDecimal(baseQty || 0).toNumber(); }
});
}
});
// Handle quantity changes in sold list rows to update conversion display
\$(document).on('input change', '.quantity-input-row', function() {
const uuid = \$(this).data('uuid');
const value = \$(this).val();
const item = soldList.find(item => item.uuid === uuid);
if (item && item.selectedUnitId) {
showUnitConversionForRow(uuid, item.productid, parseFloat(value) || 0, item.selectedUnitId);
}
// Miktar manuel değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
updateSoldListItem(uuid, 'quantity', value);
// Also refresh baseUnitQuantity on quantity change
if (item) {
computeBaseUnitQuantity(item.productid, item.selectedUnitId, parseFloat(value) || 0, function(baseQty){
const idx = soldList.findIndex(sp => sp.uuid === uuid);
if(idx !== -1){ soldList[idx].baseUnitQuantity = convertToDecimal(baseQty || 0).toNumber(); }
});
}
});
// Show unit conversion equivalent for a specific row
function showUnitConversionForRow(uuid, productId, quantity, fromUnitId) {
if (!fromUnitId || !quantity || quantity <= 0) {
updateRowQuantityHint(uuid, '');
return;
}
const product = products.find(p => p.productid === productId);
if (!product || !product.measurementUnitId) {
updateRowQuantityHint(uuid, '');
return;
}
// If already in product's main unit, no conversion needed
if (fromUnitId == product.measurementUnitId) {
updateRowQuantityHint(uuid, '');
return;
}
\$.ajax({
url: '/admin/measurement/convert-to-product-unit',
method: 'POST',
dataType: 'json',
data: {
productId: productId,
fromUnitId: fromUnitId,
quantity: quantity
},
success: function(resp) {
if (resp && resp.success) {
const unitText = product.measurementUnit ? (' ' + product.measurementUnit) : '';
updateRowQuantityHint(uuid, (Number(resp.result).toFixed(2)) + unitText);
} else {
updateRowQuantityHint(uuid, '');
}
},
error: function() {
updateRowQuantityHint(uuid, '');
}
});
}
// Update quantity hint for a specific row
function updateRowQuantityHint(uuid, text) {
let \$hint = \$(`.quantity-conversion-hint[data-uuid=\"\${uuid}\"]`);
if (\$hint.length === 0) {
const \$quantityCell = \$(`.quantity-input-row[data-uuid=\"\${uuid}\"]`).closest('td');
\$quantityCell.append(`<small class=\"form-text text-muted quantity-conversion-hint\" data-uuid=\"\${uuid}\"></small>`);
\$hint = \$(`.quantity-conversion-hint[data-uuid=\"\${uuid}\"]`);
}
\$hint.text(text || '');
}
// Convert quantity between different measurement units
function convertQuantityBetweenUnits(productId, quantity, fromUnitId, toUnitId, callback) {
if (!fromUnitId || !toUnitId || fromUnitId === toUnitId) {
callback(quantity);
return;
}
\$.ajax({
url: '/admin/measurement/convert-between-units',
method: 'POST',
dataType: 'json',
data: {
productId: productId,
fromUnitId: fromUnitId,
toUnitId: toUnitId,
quantity: quantity
},
success: function(resp) {
if (resp && resp.success) {
callback(parseFloat(resp.result));
} else {
console.error('Unit conversion failed:', resp);
callback(null);
}
},
error: function() {
console.error('Unit conversion request failed');
callback(null);
}
});
}
// Handle total price changes (reverse calculation to unit price)
\$(document).on('change blur', '.total-input-row', function() {
updateUnitPriceFromTotal(\$(this).data('uuid'));
});
// Handle delete row button clicks
const currencySymbol = \"{{ 'currency.symbol'|trans|e('js') }}\";
const PAYMENT_TOLERANCE = 0.01;
const AUTO_PAYMENT_DESCRIPTION = 'Sistem tarafından ödeme otomatik oluşturuldu';
const AUTO_PAYMENT_STATUS_TEXT = 'Ödendi';
let autoPaymentModalState = null;
let cachedCashPaymentMethod = null;
function detectCashPaymentMethod() {
const \$options = \$('#payment_paymentMethod option');
let fallback = null;
let cashCandidate = null;
\$options.each(function() {
const \$option = \$(this);
const value = (\$option.val() || '').trim();
if (!value) {
return;
}
if (!fallback) {
fallback = { id: value, label: (\$option.text() || '').trim() };
}
const label = (\$option.text() || '').toLowerCase();
if (label.includes('cash') || label.includes('nakit')) {
cashCandidate = { id: value, label: (\$option.text() || '').trim() };
return false;
}
});
if (cashCandidate) {
return cashCandidate;
}
if (fallback) {
return fallback;
}
return { id: '1', label: 'Cash' };
}
function ensureCashPaymentMethod() {
if (!cachedCashPaymentMethod) {
cachedCashPaymentMethod = detectCashPaymentMethod();
}
return cachedCashPaymentMethod;
}
function normalizeCurrency(value) {
return convertToDecimal(value).toDecimalPlaces(2, Decimal.ROUND_HALF_UP).toNumber();
}
function formatCurrency(value) {
const normalized = normalizeCurrency(value);
const formatted = addCommas(normalized.toFixed(2));
return formatted.replace(/,/g, '#').replace(/\\./g, ',').replace(/#/g, '.') + ' ' + currencySymbol;
}
function calculateTotalPaymentAmount() {
let total = new Decimal(0);
payments.forEach(function(payment) {
total = total.plus(convertToDecimal(payment.amount));
});
return total.toNumber();
}
function getSaleDateValue() {
const saleDate = \$('#sales_form_salesDate').val();
if (saleDate && saleDate.trim() !== '') {
return saleDate;
}
return new Date().toISOString().split('T')[0];
}
function createAutoPaymentPayload(amount, saleDate) {
const method = ensureCashPaymentMethod();
return {
uuid: crypto.randomUUID(),
amount: normalizeCurrency(amount),
status: '0',
paymentDate: saleDate,
paymentDueDate: saleDate,
paymentMethod: method.id,
description: AUTO_PAYMENT_DESCRIPTION
};
}
function openAutoPaymentModal(config) {
autoPaymentModalState = {
scenario: config.scenario,
amount: normalizeCurrency(config.amount),
saleDate: config.saleDate,
saleTotal: normalizeCurrency(config.saleTotal),
currentPayments: normalizeCurrency(config.currentPayments)
};
const method = ensureCashPaymentMethod();
let message = '';
if (config.scenario === 'no-payment') {
message = 'Bu satış için henüz ödeme girilmedi. Aşağıdaki bilgilerle otomatik bir nakit ödeme oluşturulacaktır.';
} else {
message = 'Girilen ödemeler satış toplamı ile uyumsuz. Eksik tutar otomatik bir nakit ödeme olarak eklenecektir.';
}
\$('#auto-payment-message').text(message);
\$('#auto-payment-sale-total').text(formatCurrency(autoPaymentModalState.saleTotal));
\$('#auto-payment-current-payments').text(formatCurrency(autoPaymentModalState.currentPayments));
\$('#auto-payment-amount').text(formatCurrency(autoPaymentModalState.amount));
\$('#auto-payment-method').text(method.label);
\$('#auto-payment-status').text(AUTO_PAYMENT_STATUS_TEXT);
\$('#auto-payment-description').text(AUTO_PAYMENT_DESCRIPTION);
\$('#confirm-auto-payment').prop('disabled', false);
\$('#autoPaymentModal').modal('show');
}
function validateSalesForm() {
const form = \$('form[name=\"sales_form\"]');
if (!form || form.length === 0) {
return true;
}
return form[0].reportValidity();
}
function submitSalesRequest() {
if (!validateSalesForm()) {
return;
}
const salesData = {
payments: payments,
soldList: soldList,
invoice: {
invoiceNumber: \$('#sales_form_invoiceNumber').val(),
customer: \$('#sales_form_customer').val(),
paymentStatus: \$('#sales_form_paymentStatus').val(),
salesDate: \$('#sales_form_salesDate').val(),
deliveryDate: \$('#sales_form_deliveryDate').val(),
dueDate: \$('#sales_form_dueDate').val(),
validDate: \$('#sales_form_validDate').val(),
seller: \$('#sales_form_seller').val(),
description: \$('#sales_form_description').val(),
shippingNotes: \$('#sales_form_shippingNotes').val(),
paymentNotes: \$('#sales_form_paymentNotes').val(),
salesType: \$('input[name=\"salesType\"]').filter(':checked').val() || 'proforma',
salesStatus: \$('#sales_form_status').val() ? \$('#sales_form_status').val() : '0'
}
};
\$.ajax({
url: \"{{ path('app_admin_sales_create') }}\",
method: 'POST',
data: salesData,
success: function(response) {
window.location.href = \"/sales/detail/\" + response.id;
Swal.fire({
icon: 'success',
title: 'Başarılı',
text: 'Satış başarıyla oluşturuldu'
});
},
error: function(xhr) {
var response = JSON.parse(xhr.responseText);
showErrorAlert(response.message);
}
});
}
function finalizeSalesSubmission() {
const saleTotal = convertToDecimal(getTotalSoldAmount());
const totalPayments = convertToDecimal(calculateTotalPaymentAmount());
const difference = saleTotal.minus(totalPayments).abs().toNumber();
if (difference > PAYMENT_TOLERANCE) {
showErrorAlert('Ödemeler satış toplamı ile hâlâ uyumsuz. Lütfen kontrol edin.');
return;
}
submitSalesRequest();
}
\$('#confirm-auto-payment').on('click', function() {
if (!autoPaymentModalState) {
return;
}
const saleDate = autoPaymentModalState.saleDate || getSaleDateValue();
const autoPayment = createAutoPaymentPayload(autoPaymentModalState.amount, saleDate);
payments.push(autoPayment);
fetchPaymentForm();
calculateAndWriteDiffBetweenPaymentAndSolds();
\$('#confirm-auto-payment').prop('disabled', true);
\$('#autoPaymentModal').modal('hide');
autoPaymentModalState = null;
finalizeSalesSubmission();
});
\$('#autoPaymentModal').on('hidden.bs.modal', function() {
\$('#confirm-auto-payment').prop('disabled', false);
autoPaymentModalState = null;
});
ensureCashPaymentMethod();
\$(document).on('click', '.delete-row-button', function() {
const uuid = \$(this).data('uuid');
deleteProductInSoldList(uuid);
\$(`tr[id=\"\${uuid}\"]`).remove();
\$(`.info-details-row-class[data-uuid=\"\${uuid}\"]`).remove();
fetchStocks();
updateTotalAmounts();
fetchPaymentForm();
\$('#sales_form_totalPurchasePrice').val(addCommas(getTotalSoldAmount()));
});
// Handle unallocated quantity modal for rows
\$(document).on('click', '.open-unallocated-modal-row', function() {
const uuid = \$(this).data('uuid');
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex === -1) return;
// Değeri gizli input yerine doğrudan soldList'ten oku
const currentQty = soldList[itemIndex].unAllocatedQuantity || 0;
\$('#modal-unallocated-quantity').val(Number(currentQty).toFixed(2));
// Store current uuid for saving later
\$('#unallocatedModal').data('current-uuid', uuid);
\$('#unallocatedModal').modal('show');
});
// Save unallocated quantity from modal for rows
\$(document).on('click', '#save-unallocated-btn', function() {
const uuid = \$('#unallocatedModal').data('current-uuid');
// Sadece satırlar için olan bölüm (uuid varsa)
if (uuid) {
const qty = \$('#modal-unallocated-quantity').val() || 0;
const newUnallocatedQty = convertToDecimal(qty).toNumber();
// 1. soldList'teki ilgili ürünü bul ve unAllocatedQuantity'yi güncelle
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex > -1) {
soldList[itemIndex].unAllocatedQuantity = newUnallocatedQty;
}
// 2. DOĞRU SATIRI HEDEFLEYEREK butondaki sayıyı güncelle
const \$button = \$(`.open-unallocated-modal-row[data-uuid=\"\${uuid}\"]`);
\$button.find('.unalloc-display').text(newUnallocatedQty.toFixed(2));
// 3. Tahsis edilmemiş miktar değiştirildi, artık otomatik hesaplama yapılmalı
delete rowHasManualTotal[uuid];
// 4. Satırın genel toplamlarını ve stokları güncelle
updateSoldProductFromRow(uuid);
\$('#unallocatedModal').modal('hide');
} else { // Bu kısım adder-row için, dokunmuyoruz
const qty = \$('#modal-unallocated-quantity').val() || 0;
\$('#adder-unallocated-quantity').val(qty).trigger('change');
\$('#unalloc-display').text(qty);
onInputChange.call(\$('#adder-unallocated-quantity')[0]);
\$('#unallocatedModal').modal('hide');
}
});
// Flag to prevent circular updates between unit price and total
let isUpdatingFromTotal = false;
let isUpdatingFromUnitPrice = false;
// adder-row input alanlarının değişikliklerini dinleme
\$('#adder-row').on('input change',
'#adder-allocated-quantity, ' +
'#adder-unallocated-quantity, ' +
'#adder-price, ' +
'#adder-discount-select, ' +
'#adder-tva-select, ' +
'#adder-total', onInputChange);
// Birim dönüşümünü yazma bittiğinde (blur) ve birim seçiminde (change) yap
\$('#adder-row').on('blur', '#adder-allocated-quantity', function(){
computeAndShowBaseUnitEquivalent();
});
\$('#adder-row').on('change', '#adder-quantity-unit-select', function(){
computeAndShowBaseUnitEquivalent();
});
// Adder rowdaki input alanlarının değişikliklerini dinleme
function onInputChange() {
if(\$(this).attr('id') === 'adder-total') {
// Toplam manuel değiştirildi → birim fiyatı hesapla, ama toplamı tekrar değiştirme
if(isUpdatingFromUnitPrice) return;
isUpdatingFromTotal = true;
const totalAmount = convertToDecimal(\$('#adder-total').val() || 0).toNumber();
const allocatedQuantity = convertToDecimal(\$('#adder-allocated-quantity').val() || 0).toNumber();
const unallocatedQuantity = convertToDecimal(\$('#adder-unallocated-quantity').val() || 0).toNumber();
const discountPercentage = convertToDecimal(((\$('#adder-discount-select').val() || '0') + '').replace('%', '')).toNumber();
const tvaPercentage = convertToDecimal(((\$('#adder-tva-select').val() || '0') + '').replace('%', '')).toNumber();
const unitPrice = calculateUnitPrice(totalAmount, allocatedQuantity, unallocatedQuantity, tvaPercentage, discountPercentage);
\$('#adder-price').val(convertToDecimal(unitPrice).toDecimalPlaces(2, MONEY_ROUNDING_MODE).toFixed(2));
setTimeout(function(){ isUpdatingFromTotal = false; }, 0);
} else {
// Birim fiyat, miktar, indirim veya KDV değişti → toplamı hesapla
if(isUpdatingFromTotal) return;
isUpdatingFromUnitPrice = true;
const allocatedQuantity = convertToDecimal(\$('#adder-allocated-quantity').val() || 0).toNumber();
const unallocatedQuantity = convertToDecimal(\$('#adder-unallocated-quantity').val() || 0).toNumber();
const price = convertToDecimal(\$('#adder-price').val() || 0).toNumber();
const discountPercentage = convertToDecimal(((\$('#adder-discount-select').val() || '0') + '').replace('%', '')).toNumber();
const tvaPercentage = convertToDecimal(((\$('#adder-tva-select').val() || '0') + '').replace('%', '')).toNumber();
calculateTotal(allocatedQuantity, unallocatedQuantity, price, tvaPercentage, discountPercentage);
setTimeout(function(){ isUpdatingFromUnitPrice = false; }, 0);
}
}
function roundToNearestCent(amount) {
return Math.round(amount * 100) / 100;
}
// Toplam tutarı hesaplama
// Her ara adımda virgülden sonra 2 hane gösterilecek, 3. hane 5 ve üzeri ise yukarı yuvarlanacak
function calculateTotal(allocatedQuantity, unallocatedQuantity, price, tvaPercentage, discountPercentage) {
const totals = calculateRowFinancials({
quantity: allocatedQuantity,
unAllocatedQuantity: unallocatedQuantity,
unitPrice: price,
discount: discountPercentage,
tax: tvaPercentage
});
\$('#adder-total').val(totals.totalRounded.toFixed(2));
}
// Toplam tutardan Birim fiyatı hesaplama
function calculateUnitPrice(totalAmount, allocatedQuantity, unallocatedQuantity, tvaPercentage, discountPercentage) {
const totalQuantity = convertToDecimal(allocatedQuantity || 0).plus(convertToDecimal(unallocatedQuantity || 0));
if (totalQuantity.isZero()) {
return 0;
}
const total = convertToDecimal(totalAmount || 0);
// Eğer toplam 0 ise, birim fiyat da 0 olmalı
if (total.isZero()) {
return 0;
}
const discountMultiplier = new Decimal(1).minus(convertToDecimal(discountPercentage || 0).div(100));
const taxMultiplier = new Decimal(1).plus(convertToDecimal(tvaPercentage || 0).div(100));
// %100 indirimde discountMultiplier = 0 olur, bu durumda birim fiyat hesaplanamaz
// Çünkü total = (unitPrice * quantity * (1 - discount) * (1 + tax))
// discount = 100% ise, unitPrice = 0 olmalı (ya da sonsuz, ama mantıklı olan 0)
if (discountMultiplier.isZero()) {
return 0;
}
if (taxMultiplier.isZero()) {
return 0;
}
// Formül: unitPrice = total / (quantity * (1 - discount%) * (1 + tax%))
const subtotal = total.div(taxMultiplier).div(discountMultiplier);
const unitPrice = subtotal.div(totalQuantity);
return unitPrice.toDecimalPlaces(6, MONEY_ROUNDING_MODE).toNumber();
}
function getProductCost(pid) {
// Popup için URL burada oluşturuluyor.
const url = '/product-cost/' + pid + '/' + getSelectedProduct().warehouseid;
\$.ajax({
url: \"/product-cost-detail/\" + pid + '?warehouse=' + getSelectedProduct().warehouseid,
method: \"POST\",
dataType: \"JSON\",
success: function(data) {
var product = getSelectedProduct();
product.cost = data.cost;
product.measurementUnit = data.measurement;
\$('.stock-info-span').html(data.stock.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" \" + data.measurement);
\$('.cost-info-span').html(data.cost.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" €\"); // Eski detay butonu kaldırıldı
\$('.measurement-unit-info-span').html(data.measurement);
\$('#hidden-cost-input').val(data.cost);
// \"Maliyet\" popup açıyor maliyet detaylarını gösteriyor.
const detailsHtml = `
<div class=\"row align-items-center\">
<div class=\"col-md-3\">\${product.name}</div>
<div class=\"col-md-3\">
<strong>
<button type=\"button\" class=\"btn btn-link btn-sm p-0 cost-popup-btn\" data-url=\"\${url}\">Maliyet: \${data.cost.toFixed(2)} €</button>
</strong>
</div>
<div class=\"col-md-3\"><strong>Stok:</strong> \${data.stock.toFixed(2)} \${data.measurement}</div>
<div class=\"col-md-3\"><strong>Birim:</strong> \${data.measurement}</div>
</div>
`;
\$('#info-details-content').html(detailsHtml);
return data;
},
error: function(data) {
\$('#info-details-content').html('<div class=\"text-danger\">Ürün maliyet bilgileri yüklenemedi.</div>');
}
});
}
\$(document).on('click', '.cost-popup-btn', function() {
const url = \$(this).data('url');
if (url) {
createPopup(url, 'cost-detail-window');
}
});
// Unallocated modal açıldığında mask'i uygula
\$('#unallocatedModal').on('shown.bs.modal', function() {
applyMasks();
});
// Unallocated modal aç/kapat ve değerleri senkronize et
\$(document).on('click', '#open-unallocated-modal', function() {
// Mevcut değerleri moda aktarma
const currentQty = \$('#adder-unallocated-quantity').val() || 0;
const currentUnit = \$('#adder-unallocated-quantity-unit-select').val() || '';
\$('#modal-unallocated-quantity').val(Number(currentQty).toFixed(2));
// Seçili ürünün saklanan birimlerini modal select'ine doldur
const selectedProduct = getSelectedProduct();
if (selectedProduct && selectedProduct.convertibleUnits) {
populateSelectWithUnits(\$('#modal-unallocated-unit'), selectedProduct.convertibleUnits);
} else {
\$('#modal-unallocated-unit').empty().append('<option value=\"\">-</option>');
}
\$('#modal-unallocated-unit').val(currentUnit);
\$('#unallocatedModal').modal('show');
});
// 'Tahsis Edilmemiş Miktar' Modal'ındaki Kaydet Butonunun Düzeltilmiş Hali
\$(document).on('click', '#save-unallocated-btn', function() {
const uuid = \$('#unallocatedModal').data('current-uuid');
const qty = \$('#modal-unallocated-quantity').val() || 0;
const newUnallocatedQty = convertToDecimal(qty).toNumber();
// DURUM 1: Satış listesindeki bir satır güncelleniyor (uuid mevcut)
if (uuid) {
// 1. soldList'teki ilgili ürünü bul ve unAllocatedQuantity'yi güncelle
const itemIndex = soldList.findIndex(item => item.uuid === uuid);
if (itemIndex > -1) {
soldList[itemIndex].unAllocatedQuantity = newUnallocatedQty;
}
// 2. Sadece ilgili satırı ID ile hedefle ve içindeki göstergeyi güncelle
const \$row = \$(`#\${uuid}`);
\$row.find('.unalloc-display').text(newUnallocatedQty.toFixed(2));
// 3. Satırın genel toplamlarını ve stokları güncelle
updateSoldProductFromRow(uuid);
}
// DURUM 2: Adder-row (ekleme satırı) güncelleniyor (uuid mevcut değil)
else {
// Sadece adder-row'daki gizli input'u ve göstergeyi güncelle
\$('#adder-unallocated-quantity').val(newUnallocatedQty).trigger('change');
\$('#unalloc-display').text(newUnallocatedQty.toFixed(2));
// Adder-row'daki toplamı yeniden hesapla
onInputChange.call(\$('#adder-unallocated-quantity')[0]);
}
// İşlem tamamlandıktan sonra modalı kapat
\$('#unallocatedModal').modal('hide');
// Modal'daki uuid verisini temizle ki bir sonraki açılışta karışmasın
\$('#unallocatedModal').removeData('current-uuid');
});
function formatState(state) {
if (!state.id) {
return state.text;
}
var baseUrl = \"/images\";
var \$state = \$(
'<span><img src=\"' + baseUrl + '/' + state.element.dataset.image + '\" class=\"img-flag list-image\" style=\"width: 50px;\"/> ' + state.text + '</span>'
);
return \$state;
};
\$('#productselect').select2({
theme: 'bootstrap4',
dropdownCssClass: 'custom-select-dropdown',
placeholder: 'Ürün Seçin',
allowClear: true
});
function showErrorAlert(message) {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: '' + message,
background: 'white',
});
}
window.addEventListener(\"error\", function(event) {
showErrorAlert(event.message);
});
function showSuccessAlert(message) {
Swal.fire({
icon: 'success',
title: 'Başarılı',
text: '' + message,
background: 'white',
});
}
\$('#submitbtn').on('click', function(event) {
event.preventDefault();
if (soldList.length === 0) {
showErrorAlert(\"{% trans %} sale.errors.cannot_save_without_products {% endtrans %}\");
return;
}
if (!validateSalesForm()) {
return;
}
const saleTotalValue = convertToDecimal(getTotalSoldAmount()).toNumber();
const currentPaymentTotal = convertToDecimal(calculateTotalPaymentAmount()).toNumber();
const saleDate = getSaleDateValue();
if (payments.length === 0 && saleTotalValue > PAYMENT_TOLERANCE) {
openAutoPaymentModal({
scenario: 'no-payment',
amount: saleTotalValue,
saleTotal: saleTotalValue,
currentPayments: currentPaymentTotal,
saleDate: saleDate
});
return;
}
const diffValue = convertToDecimal(saleTotalValue).minus(convertToDecimal(currentPaymentTotal)).toNumber();
if (Math.abs(diffValue) > PAYMENT_TOLERANCE) {
if (diffValue < 0) {
showErrorAlert('Girilen ödeme tutarı satış tutarını aşıyor. Lütfen ödeme kayıtlarını kontrol edin.');
return;
}
openAutoPaymentModal({
scenario: 'mismatch',
amount: diffValue,
saleTotal: saleTotalValue,
currentPayments: currentPaymentTotal,
saleDate: saleDate
});
return;
}
submitSalesRequest();
});
function addCommas(number) {
let decimals = 2;
number = (number + '').replace(/[^0-9+\\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = ',';
var dec = '.';
var s = '';
var toFixedFix = function(n, prec) {
var k = Math.pow(10, prec);
return '' + (Math.round(n * k) / k).toFixed(prec);
};
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (s[0].length > 3) {
s[0] = s[0].replace(/\\B(?=(?:\\d{3})+(?!\\d))/g, sep);
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
\$(document).on('input change', '#products_sold_totalUnitPurchasePrice, #products_sold_quantity, #products_sold_unAllocatedQuantity, #products_sold_discount, #products_sold_tax', function() {
var purchasePrice = \$('#products_sold_totalUnitPurchasePrice').val() || \"0\";
var purchasePriceClean = parseFloat(purchasePrice.replace(/,/g, \"\")) || 0;
var cost = parseFloat(\$('#hidden-cost-input').val()) || 0;
var quantity = \$('#products_sold_quantity').val() || \"0\";
var quantityClean = parseFloat(quantity.replace(/,/g, \"\")) || 0;
var unAllocatedQuantity = \$('#products_sold_unAllocatedQuantity').val() || \"0\";
var unAllocatedQuantityClean = parseFloat(unAllocatedQuantity.replace(/,/g, \"\")) || 0;
var discountPercentage = \$('#products_sold_discount').val() || \"0\";
var discountPercentageClean = parseFloat(discountPercentage.replace(/,/g, \"\")) || 0;
var taxPercentage = \$('#products_sold_tax').val() || \"0\";
var taxPercentageClean = parseFloat(taxPercentage.replace(/,/g, \"\")) || 0;
var totalQuantity = quantityClean + unAllocatedQuantityClean;
var totalSalePriceBeforeDiscount = purchasePriceClean * totalQuantity;
var discountAmount = (totalSalePriceBeforeDiscount * discountPercentageClean) / 100;
var totalSalePriceAfterDiscount = totalSalePriceBeforeDiscount - discountAmount;
var taxAmount = (totalSalePriceAfterDiscount * taxPercentageClean) / 100;
var totalSalePrice = totalSalePriceAfterDiscount + taxAmount;
\$('#products_sold_totalSalePrice').val(addCommas(totalSalePrice.toFixed(2)));
\$('#products_sold_totalPuchasePrice').val(addCommas(totalSalePrice.toFixed(2)));
cost = roundToDecimal(cost);
var salePriceWithoutTax = totalSalePrice / (1 + taxPercentageClean / 100);
var totalCost = totalQuantity * cost;
var profit = salePriceWithoutTax - totalCost;
var profitSpan;
if (profit > 0) {
profitSpan = \"<span style='color:green'>\" + profit.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" €</span>\";
} else {
profitSpan = \"<span style='color:red'>\" + profit.toFixed(2).toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\") + \" €</span>\";
}
\$('.profit-info-span').html(profitSpan);
});
\$('#payment_amount').mask(\"#,##0.00\", {
reverse: true
});
// Show/Hide Voucher Code field based on Payment Method
\$('#payment_paymentMethod').on('change', function() {
var selectedText = \$(this).find(\"option:selected\").text().trim().toLowerCase();
var \$voucherField = \$('#payment_voucherCode');
var \$voucherLabel = \$('label[for=\"payment_voucherCode\"]');
if (selectedText.includes('hediye') || selectedText.includes('gift') || selectedText.includes('çek')) {
\$voucherField.show();
\$voucherLabel.show();
\$voucherField.attr('required', 'required');
} else {
\$voucherField.hide();
\$voucherLabel.hide();
\$voucherField.val('');
\$voucherField.removeAttr('required');
}
});
\$('#sales_form_customer').select2({
theme: 'bootstrap4',
dropdownCssClass: 'custom-select-dropdown',
placeholder: 'Müşteri Seçin',
allowClear: true
});
\$('.js-edit-customer').on('click', function() {
let customerId = \$('#sales_form_customer').val();
if (!customerId) {
showErrorAlert(\"{% trans %} selectCustomerForEdit {% endtrans %}\");
} else {
\$.ajax({
url: '/admin/customer/detail/' + customerId,
type: 'GET',
dataType: 'json',
success: function(response) {
showUpdateModal(customerId, response);
},
error: function(jqXHR, textStatus, errorThrown) {
if (jqXHR.status === 404) {
showErrorAlert(\"Seçilen müşteri veritabanında bulunamadı.\");
} else {
showErrorAlert('Müşteri bilgileri yüklenirken bir hata oluştu.');
}
clearCustomerFields();
}
});
}
});
\$('#sales_form_customer').on('change', function() {
let customerId = \$(this).val();
if (!customerId) {
clearCustomerFields();
return;
}
\$.ajax({
url: '/admin/customer/detail/' + customerId,
type: 'GET',
dataType: 'json',
success: function(response) {
if (!response.phone || !response.email || !response.address) {
showUpdateModal(customerId, response);
} else {
populateCustomerFields(response);
}
},
error: function(jqXHR, textStatus, errorThrown) {
if (jqXHR.status === 404) {
showErrorAlert(\"Seçilen müşteri veritabanında bulunamadı.\");
} else {
showErrorAlert('Müşteri bilgileri yüklenirken bir hata oluştu.');
}
clearCustomerFields();
}
});
});
function populateCustomerFields(customer) {
\$('#update_customer_fullName').val(customer.fullName);
\$('#update_customer_email').val(customer.email);
\$('#update_customer_phone').val(customer.phone);
\$('#update_customer_address').val(customer.address);
}
function clearCustomerFields() {
\$('#update_customer_fullName, #update_customer_email, #update_customer_phone, #update_customer_address').val('');
}
function showUpdateModal(id, customer) {
\$('#update_customer_id').val(id);
\$('#update_customer_email').val(customer.email || '');
\$('#update_customer_phone').val(customer.phone || '');
\$('#update_customer_address').val(customer.address || '');
\$('#update_customer_fullName').val(customer.fullName || '');
\$('#updateCustomerModal').modal('show');
}
\$('#updateCustomerForm').on('submit', function(e) {
e.preventDefault();
let customerId = \$('#update_customer_id').val();
let formData = {
email: \$('#update_customer_email').val(),
phone: \$('#update_customer_phone').val(),
address: \$('#update_customer_address').val(),
fullName: \$('#update_customer_fullName').val()
};
\$.ajax({
url: '/admin/customer/update/' + customerId,
type: 'POST',
data: formData,
success: function(response) {
\$('#updateCustomerModal').modal('hide');
populateCustomerFields(response);
showSuccessAlert(\"{% trans %} customerUpdateSuccess {% endtrans %}\");
},
error: function(jqXHR, textStatus, errorThrown) {
let errorMessage = 'Güncelleme sırasında bilinmeyen bir hata oluştu.';
try {
const response = JSON.parse(jqXHR.responseText);
if (response && response.message) {
errorMessage = response.message;
}
} catch (e) {
console.error(\"JSON Parse Error:\", e);
console.error(\"Server Response:\", jqXHR.responseText);
}
showErrorAlert(errorMessage);
}
});
});
\$('#show-info-btn').on('click', function(e) {
e.preventDefault();
const selectedProductId = \$('#productselect').val();
if (!selectedProductId || selectedProductId <= 0) {
showErrorAlert(\"Detayları görmek için önce bir ürün seçmelisiniz.\");
return;
}
const \$infoContent = \$('#info-details-content');
const \$icon = \$(this).find('i');
\$infoContent.toggle();
if (\$infoContent.is(':visible')) {
\$icon.removeClass('fa-info-circle').addClass('fa-chevron-up');
} else {
\$icon.removeClass('fa-chevron-up').addClass('fa-info-circle');
}
});
// --- GIFT VOUCHER LOGIC ---
// Hediye Kuponu Butonuna Tıklama (Desktop)
\$('#btn-gift-voucher').on('click', function() {
openGiftVoucherModal();
});
// Hediye Kuponu Butonuna Tıklama (Mobile)
\$('#btn-gift-voucher-mobile').on('click', function() {
openGiftVoucherModal();
});
function openGiftVoucherModal() {
const customerId = \$('#sales_form_customer').val();
if (!customerId) {
showErrorAlert('Lütfen önce bir müşteri seçiniz.');
return;
}
// API'den bakiye sorgula
\$.ajax({
url: '/admin/gift-certificates/api/balance/' + customerId,
method: 'GET',
success: function(response) {
if (response.error) {
showErrorAlert(response.error);
return;
}
\$('#voucher-total-balance').text(response.formatted);
\$('#voucher-total-balance').data('balance', response.balance);
\$('#voucher-customer-name').text(response.customerName); // Set customer name
\$('#voucher-use-amount').val(''); // Inputu temizle
// Varsayılan olarak kalanı doldurmayı deneyebiliriz ama kullanıcı girişi daha iyi
const totals = aggregateSoldTotals();
const paymentTotal = convertToDecimal(calculateTotalPaymentAmount());
const remaining = totals.grandTotal.minus(paymentTotal);
if (remaining.greaterThan(0)) {
// Eğer kalan miktar, bakiyeden küçükse kalanı, büyükse bakiyeyi öner
const balance = new Decimal(response.balance);
const suggested = remaining.lessThan(balance) ? remaining : balance;
\$('#voucher-use-amount').val(suggested.toNumber());
}
\$('#gift-voucher-usage-modal').modal('show');
},
error: function() {
showErrorAlert('Müşteri hediye çeki bakiyesi sorgulanırken bir hata oluştu.');
}
});
}
// Hediye Çeki Kullanımını Onayla
\$('#btn-confirm-voucher-usage').on('click', function() {
const amount = parseFloat(\$('#voucher-use-amount').val());
const balance = parseFloat(\$('#voucher-total-balance').data('balance'));
if (isNaN(amount) || amount <= 0) {
showErrorAlert('Lütfen geçerli bir miktar giriniz.');
return;
}
if (amount > balance) {
showErrorAlert('Girdiğiniz miktar, toplam bakiyeden fazla olamaz.');
return;
}
// Ödeme Yöntemini Bul (Hediye Çeki / Gift Voucher)
let voucherMethodId = null;
// 1. Yöntem: Backend'den gelen veriyi kullan (En Sağlam)
// 1. Yöntem: Backend'den gelen veriyi kullan (En Sağlam)
{% if paymentMethods is defined %}
const methods = [
{% for pm in paymentMethods %}
{ id: \"{{ pm.id }}\", name: \"{{ pm.name|e('js') }}\" },
{% endfor %}
];
const voucherMethod = methods.find(m =>
(m.name && m.name.toLowerCase().includes('hediye')) ||
(m.name && m.name.toLowerCase().includes('gift')) ||
(m.name && m.name.toLowerCase().includes('kupon'))
);
if (voucherMethod) {
voucherMethodId = voucherMethod.id;
}
{% endif %}
// 2. Yöntem: Eğer yukarıdaki çalışmazsa DOM'dan bulmayı dene
if (!voucherMethodId) {
const \$methodSelect = \$('select[name=\"paymentMethod[]\"]').first(); // Tablodaki herhangi bir select
\$methodSelect.find('option').each(function() {
const text = \$(this).text().toLowerCase();
if (text.includes('hediye') || text.includes('gift') || text.includes('kupon')) {
voucherMethodId = \$(this).val();
return false; // break
}
});
}
// 3. Yöntem: Modal Select
if (!voucherMethodId) {
const \$modalSelect = \$('#add-payment-modal select[name=\"paymentMethod\"]');
\$modalSelect.find('option').each(function() {
const text = \$(this).text().toLowerCase();
if (text.includes('hediye') || text.includes('gift') || text.includes('kupon')) {
voucherMethodId = \$(this).val();
return false; // break
}
});
}
if (!voucherMethodId) {
// Eğer modal select'inde yoksa, bir de manuel tanımlı ID varsa onu kontrol et (opsiyonel)
// Veya uyarı ver
// Fallback: İlk metodun id'sini alma riskine girmeyelim, kullanıcıya soralım veya hata verelim.
// Ancak genelde bir \"Hediye Çeki\" metodu olmalı.
// Şimdilik hata verelim.
showErrorAlert('Sistemde \"Hediye Çeki\" ödeme yöntemi bulunamadı. Lütfen yönetim panelinden ekleyiniz.');
return;
}
// Yeni ödeme satırı ekle
const payment = {
uuid: crypto.randomUUID(),
amount: amount,
status: \"0\", // Ödendi
paymentDate: new Date().toISOString().split('T')[0], // Bugün
paymentDueDate: new Date().toISOString().split('T')[0], // Bugün
paymentMethod: voucherMethodId,
description: 'Hediye Çeki Kullanımı',
voucherCode: 'AUTO_DEDUCT_FROM_BALANCE' // Backend bunu işleyip ilgili çeklerden düşebilir
};
payments.push(payment);
fetchPaymentForm();
fetchSoldListRows(); // Trigger AVOIR row update
\$('#gift-voucher-usage-modal').modal('hide');
showSuccessAlert(amount + ' TL hediye çeki ödemesi eklendi.');
});
// calculateTotalPaymentAmount fonksiyonunu global scope'ta bulamadıysak buraya ekleyelim veya mevcut olanı kullanalım
function calculateTotalPaymentAmount() {
let total = new Decimal(0);
payments.forEach(function(p) {
total = total.plus(convertToDecimal(p.amount));
});
return total;
}
// --- END GIFT VOUCHER LOGIC ---
});
function createPopup(url, window_name = 'example-popup') {
var width = 1100;
var height = 700;
var screen_width = window.screen.width;
var screen_height = window.screen.height;
var popup_left = (screen_width - width) / 2;
var popup_top = (screen_height - height) / 2;
var popup_window = window.open(url, window_name, 'width=' + width + ',height=' + height + ',left=' + popup_left + ',top=' + popup_top);
}
</script>
{% endblock %}
", "admin/sales/index.html.twig", "/app/templates/admin/sales/index.html.twig");
}
}