first commit

This commit is contained in:
O K
2025-09-13 12:09:42 +03:00
commit 72bfbef4a8
6 changed files with 816 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
$(document).ready(function() {
const productsBody = $('#fiscal-products-body');
const paymentsContainer = $('#fiscal-payments-container');
const addPaymentBtn = $('#add-payment-btn');
const paymentRowTemplate = $('#payment-row-template');
let paymentIndex = 0; // A counter for unique payment input names
// --- Core Calculation Function ---
function updateTotals() {
let productsTotal = 0;
productsBody.find('.product-row').each(function() {
const quantity = parseFloat($(this).find('.fiscal-quantity').val()) || 0;
const price = parseFloat($(this).find('.fiscal-price').val()) || 0;
const rowTotal = quantity * price;
$(this).find('.fiscal-row-total').text(rowTotal.toFixed(2));
productsTotal += rowTotal;
});
let discountsTotal = 0;
$('.discount-row').each(function() {
const discountValue = parseFloat($(this).find('.fiscal-discount-value').text().replace(',', '.')) || 0;
discountsTotal += Math.abs(discountValue);
});
const grandTotal = productsTotal - discountsTotal;
let paymentsTotal = 0;
paymentsContainer.find('.payment-row').each(function() {
const paymentAmount = parseFloat($(this).find('.payment-amount').val()) || 0;
paymentsTotal += paymentAmount;
});
// Update summary display
$('#fiscal-products-total').text(productsTotal.toFixed(2));
$('#fiscal-discounts-total').text(discountsTotal.toFixed(2));
$('#fiscal-grand-total').text(grandTotal.toFixed(2));
$('#fiscal-payments-total').text(paymentsTotal.toFixed(2));
const mismatchError = $('#fiscal-total-mismatch-error');
const createCheckBtn = $('#create-fiscal-check-btn');
if (Math.abs(grandTotal - paymentsTotal) > 0.001) {
mismatchError.show();
createCheckBtn.prop('disabled', true);
} else {
mismatchError.hide();
createCheckBtn.prop('disabled', grandTotal <= 0);
}
}
// --- Event Handlers ---
$('form').on('input', '.fiscal-quantity, .fiscal-price', updateTotals);
paymentsContainer.on('input', '.payment-amount', updateTotals);
addPaymentBtn.on('click', function() {
const newRow = $(paymentRowTemplate.html());
newRow.find('.payment-type').attr('name', `payments[${paymentIndex}][type]`);
newRow.find('.payment-amount').attr('name', `payments[${paymentIndex}][value]`);
paymentsContainer.append(newRow);
if (paymentsContainer.find('.payment-row').length === 1) {
const grandTotal = parseFloat($('#fiscal-grand-total').text());
if (grandTotal > 0) {
newRow.find('.payment-amount').val(grandTotal.toFixed(2));
}
}
paymentIndex++;
updateTotals();
});
paymentsContainer.on('click', '.remove-payment-btn', function() {
$(this).closest('.payment-row').remove();
updateTotals();
});
// --- Initial State ---
updateTotals();
addPaymentBtn.trigger('click');
});

View File

@@ -0,0 +1,199 @@
<script src="/modules/checkprestabox/views/js/admin_order_fiscal.js"></script>
<div class="tab-pane fade " id="checkprestaboxTabContent" role="tabpanel" aria-labelledby="checkprestaboxTab">
<div class="card mt-2">
<div class="card-header"></div>
<div class="card-body">
{% if checkprestaboxFiscals is not empty %}
<h4>Існуючі фіскальні чеки:</h4>
<ul>
{% for fiscal in checkprestaboxFiscals %}
<li>
<a target="_blanc" href="https://check.checkbox.ua/{{ fiscal.transaction_id }}">
Чек #
{{ fiscal.transaction_id }}
від
{{ fiscal.date_add }}
<i class="material-icons">open_in_new</i>
</a>
</li>
{% endfor %}
</ul>
<hr>
{% endif %}
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#fiscalModal">
Створити фіскальний чек
</button>
</div>
</div>
<!-- Fiscalization Modal -->
<div class="modal fade" id="fiscalModal" tabindex="-1" role="dialog" aria-labelledby="fiscalModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="fiscalModalLabel">Створення фіскального чеку</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form method="post" action="#checkprestaboxTabContent">
<div class="card mt-2">
<h3 class="card-header">
<i class="material-icons">receipt</i>
Створення фіскального чеку
</h3>
<div
class="card-body">
{# Products Section #}
<h6>
<textarea rows="1" class="form-control " name="header">{{ checkprestaboxFiscalForm.header }}</textarea>
</h6>
<h6>Товари в чеку</h6>
<table class="table table-bordered">
<thead>
<tr>
<th>Назва</th>
<th>Код (Артикул)</th>
<th style="width: 100px;">Кількість</th>
<th style="width: 120px;">Ціна за од.</th>
<th class="text-right" style="width: 120px;">Сума</th>
</tr>
</thead>
<tbody id="fiscal-products-body">
{% for product in checkprestaboxFiscalForm.products %}
<tr class="product-row">
<td>{{ product.good.name }}</td>
<td>{{ product.good.code }}</td>
<td>
{# HIDDEN inputs to pass non-editable data #}
<input type="hidden" name="products[{{ loop.index0 }}][name]" value="{{ product.good.name }}">
<input
type="hidden" name="products[{{ loop.index0 }}][code]" value="{{ product.good.code }}">
{# EDITABLE inputs #}
<input type="number" class="form-control fiscal-quantity" name="products[{{ loop.index0 }}][quantity]" value="{{ product.quantity }}" min="0">
</td>
<td><input type="number" step="0.01" class="form-control fiscal-price" name="products[{{ loop.index0 }}][price]" value="{{ product.good.price|number_format(2, '.', '') }}" min="0"></td>
<td class="text-right">
<span class="fiscal-row-total">0.00</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{# Discounts Section #}
{% if checkprestaboxFiscalForm.discounts is not empty %}
<h6>Знижки</h6>
<table class="table table-sm">
<tbody>
{% for discount in checkprestaboxFiscalForm.discounts %}
<tr class="discount-row">
<td>{{ discount.name }}</td>
<td class="text-right fiscal-discount-value">-{{ discount.value|number_format(2, '.', '') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<hr>
{# Summary and Payments Section #}
<div class="row">
<div class="col-md-6">
<h6>Оплати</h6>
<div
id="fiscal-payments-container">{# Payment rows will be inserted here by JavaScript #}
</div>
<button type="button" id="add-payment-btn" class="btn btn-sm btn-outline-primary mt-2">
<i class="material-icons">add_circle_outline</i>
Додати оплату
</button>
</div>
<div class="col-md-6">
<table class="table">
<tbody>
<tr>
<td>Всього за товари:</td>
<td class="text-right font-weight-bold">
<span id="fiscal-products-total">0.00</span>
грн</td>
</tr>
<tr>
<td>Знижки:</td>
<td class="text-right font-weight-bold">-<span id="fiscal-discounts-total">0.00</span>
грн</td>
</tr>
<tr class="bg-light">
<td class="font-weight-bold">ДО СПЛАТИ (РАЗОМ):</td>
<td class="text-right font-weight-bold">
<span id="fiscal-grand-total">0.00</span>
грн</td>
</tr>
<tr>
<td>Сплачено:</td>
<td class="text-right font-weight-bold">
<span id="fiscal-payments-total">0.00</span>
грн</td>
</tr>
</tbody>
</table>
<div id="fiscal-total-mismatch-error" class="alert alert-danger" style="display: none;">
<strong>Помилка:</strong>
Сума оплат не збігається з підсумковою сумою до сплати.
</div>
</div>
</div>
</div>
<div class="card-footer">
<h6>
<textarea rows="2" class="form-control " name="footer">{{ checkprestaboxFiscalForm.footer }}</textarea>
</h6>
<div
class="d-flex justify-content-end">
{# The important submit button #}
<button type="submit" name="checkprestaboxNewFiscal" id="create-fiscal-check-btn" class="btn btn-primary" disabled>
Створити та фіскалізувати
</button>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Закрити</button>
</div>
</div>
</div>
</div>
{# This invisible template is used by JavaScript to create new payment rows #}
<template id="payment-row-template">
<div class="form-row align-items-center mb-2 payment-row">
<div
class="col-5">
{# The name attribute will be set by JavaScript #}
<select class="form-control payment-type">
<option value="Платіж через інтегратора НоваПей" data-label="Платіж через інтегратора НоваПей">НоваПей</option>
<option value="Платіж через інтегратора Liqpay" data-label="Платіж через інтегратора Liqpay">Liqpay</option>
<option value="Платіж через інтегратора RozetkaPay" data-label="Платіж через інтегратора RozetkaPay">RozetkaPay</option>
<option value="Платіж через інтегратора mono" data-label="Платіж через інтегратора mono">mono</option>
<option value="Готівка" data-label="Готівка">Готівка</option>
</select>
</div>
<div
class="col-5">
{# The name attribute will be set by JavaScript #}
<input type="number" step="0.01" class="form-control payment-amount" placeholder="Сума" min="0">
</div>
<div class="col-2">
<button type="button" class="btn btn-danger btn-sm remove-payment-btn">&times;</button>
</div>
</div>
</template>
</div>

View File

@@ -0,0 +1,6 @@
<li class="nav-item">
<a class="nav-link" id="checkprestaboxTab" data-toggle="tab" href="#checkprestaboxTabContent" role="tab" aria-controls="checkprestaboxTabContent" aria-expanded="true" aria-selected="false">
<i class="material-icons">app_registration</i>
Checkbox
</a>
</li>