feat(nabavka): kalkulacija — formiranje prodajne cene pri nabavci (Faza B)
Po stavci nabavke: marža% (predpopunjena globalnom iz podešavanja, kalkulacija_marza) i prodajna cena = nabavna × (1+marža/100) × (1+PDV/100), živo računato u Alpine, izmenjivo. Na čuvanje se ažurira nabavna+prodajna cena artikla (ArtikalRepo.AzurirajCene) i upiše nivelacija 'kalkulacija'; prazna/nulta prodajna se preskače. ArtikliJSON nosi pdv_stopa. Postavka podrazumevane marže u Podešavanja → Sistem.
This commit is contained in:
+17
-2
@@ -186,8 +186,9 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
// forma za nabavku
|
||||
Alpine.data('nabavkaForma', () => ({
|
||||
stavke: [{artikal_id: '', kolicina: 1, cena: 0}],
|
||||
stavke: [{artikal_id: '', kolicina: 1, cena: 0, marza: 0, prodajna: 0}],
|
||||
artikliOpcije: [],
|
||||
marzaDefault: 0,
|
||||
isMobile: false,
|
||||
modal: false,
|
||||
modalUcitavanje: false,
|
||||
@@ -212,13 +213,27 @@ document.addEventListener('alpine:init', () => {
|
||||
modalDobNapomena: '',
|
||||
init() {
|
||||
this.artikliOpcije = window._ntechArtikli || []
|
||||
this.marzaDefault = parseFloat(window._ntechMarza) || 0
|
||||
this.stavke.forEach(s => { s.marza = this.marzaDefault })
|
||||
this.isMobile = window.matchMedia('(max-width: 768px)').matches
|
||||
window.matchMedia('(max-width: 768px)').addEventListener('change', e => {
|
||||
this.isMobile = e.matches
|
||||
})
|
||||
},
|
||||
dodajStavku() {
|
||||
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0})
|
||||
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0, marza: this.marzaDefault, prodajna: 0})
|
||||
},
|
||||
// PDV stopa izabranog artikla (iz JSON liste) — za obračun prodajne cene
|
||||
pdvStopa(artikalId) {
|
||||
const a = this.artikliOpcije.find(x => String(x.id) === String(artikalId))
|
||||
return a ? (parseFloat(a.pdv_stopa) || 0) : 0
|
||||
},
|
||||
// prodajna (sa PDV) = nabavna × (1 + marža/100) × (1 + pdvStopa/100), zaokruženo na 2 decimale
|
||||
izracunajProdajnu(s) {
|
||||
const cena = parseFloat(s.cena) || 0
|
||||
const marza = parseFloat(s.marza) || 0
|
||||
const pdv = this.pdvStopa(s.artikal_id)
|
||||
s.prodajna = Math.round(cena * (1 + marza / 100) * (1 + pdv / 100) * 100) / 100
|
||||
},
|
||||
ukloniStavku(i) {
|
||||
if (this.stavke.length > 1) this.stavke.splice(i, 1)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{{define "sadrzaj"}}
|
||||
|
||||
<!-- lista artikala kao JSON — bezbedno serijalizovana na serveru -->
|
||||
<script>var _ntechArtikli = {{.ArtikliJSON}};</script>
|
||||
<script>var _ntechArtikli = {{.ArtikliJSON}}; var _ntechMarza = {{.Marza}};</script>
|
||||
|
||||
<div style="width:100%;" x-data="nabavkaForma">
|
||||
|
||||
@@ -87,6 +87,8 @@
|
||||
<th style="padding:8px 10px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Artikal</th>
|
||||
<th style="padding:8px 10px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:90px;">Količina</th>
|
||||
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:130px;">Cena/kom (din)</th>
|
||||
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:90px;">Marža %</th>
|
||||
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:130px;">Prodajna/kom (din)</th>
|
||||
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:110px;">Ukupno</th>
|
||||
<th style="width:40px;"></th>
|
||||
</tr>
|
||||
@@ -96,7 +98,7 @@
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:8px 10px;">
|
||||
<select :name="'artikal_id[]'" x-model="stavka.artikal_id"
|
||||
:disabled="isMobile" style="width:100%;">
|
||||
@change="izracunajProdajnu(stavka)" :disabled="isMobile" style="width:100%;">
|
||||
<option value="">— odaberi artikal —</option>
|
||||
<template x-for="a in artikliOpcije" :key="a.id">
|
||||
<option :value="a.id" x-text="a.naziv"></option>
|
||||
@@ -109,6 +111,16 @@
|
||||
</td>
|
||||
<td style="padding:8px 10px;">
|
||||
<input type="number" :name="'cena_po_komadu[]'" x-model="stavka.cena"
|
||||
@input="izracunajProdajnu(stavka)"
|
||||
min="0" step="0.01" :disabled="isMobile" style="width:100%;text-align:right;">
|
||||
</td>
|
||||
<td style="padding:8px 10px;">
|
||||
<input type="number" :name="'marza[]'" x-model="stavka.marza"
|
||||
@input="izracunajProdajnu(stavka)"
|
||||
min="0" step="0.01" :disabled="isMobile" style="width:100%;text-align:right;">
|
||||
</td>
|
||||
<td style="padding:8px 10px;">
|
||||
<input type="number" :name="'prodajna[]'" x-model="stavka.prodajna"
|
||||
min="0" step="0.01" :disabled="isMobile" style="width:100%;text-align:right;">
|
||||
</td>
|
||||
<td style="padding:8px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
@@ -127,7 +139,7 @@
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr style="border-top:0.5px solid var(--ivica);">
|
||||
<td colspan="3" style="padding:10px 10px;text-align:right;font-size:13px;color:var(--tekst-sporedni);font-weight:500;">Ukupno:</td>
|
||||
<td colspan="5" style="padding:10px 10px;text-align:right;font-size:13px;color:var(--tekst-sporedni);font-weight:500;">Ukupno:</td>
|
||||
<td style="padding:10px 10px;text-align:right;font-size:15px;font-weight:600;color:var(--tekst-glavni);">
|
||||
<span x-text="ukupnoSvega() + ' din'"></span>
|
||||
</td>
|
||||
@@ -154,7 +166,7 @@
|
||||
<div>
|
||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Artikal</label>
|
||||
<select :name="'artikal_id[]'" x-model="stavka.artikal_id"
|
||||
:disabled="!isMobile" style="width:100%;">
|
||||
@change="izracunajProdajnu(stavka)" :disabled="!isMobile" style="width:100%;">
|
||||
<option value="">— odaberi artikal —</option>
|
||||
<template x-for="a in artikliOpcije" :key="a.id">
|
||||
<option :value="a.id" x-text="a.naziv"></option>
|
||||
@@ -168,7 +180,17 @@
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Cena/kom (din)</label>
|
||||
<input type="number" :name="'cena_po_komadu[]'" x-model="stavka.cena" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
|
||||
<input type="number" :name="'cena_po_komadu[]'" x-model="stavka.cena" @input="izracunajProdajnu(stavka)" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
||||
<div>
|
||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Marža %</label>
|
||||
<input type="number" :name="'marza[]'" x-model="stavka.marza" @input="izracunajProdajnu(stavka)" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Prodajna/kom (din)</label>
|
||||
<input type="number" :name="'prodajna[]'" x-model="stavka.prodajna" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
|
||||
@@ -56,6 +56,27 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:16px;border-top:0.5px solid var(--ivica);padding-top:16px;">
|
||||
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);margin-bottom:10px;">Kalkulacija</div>
|
||||
<form method="POST" action="/podesavanja/sacuvaj">
|
||||
<input type="hidden" name="_next" value="/admin/podesavanja/sistem">
|
||||
<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-end;">
|
||||
<div>
|
||||
<label for="kalkulacija_marza" class="polje-labela">Podrazumevana marža (%)</label>
|
||||
<input type="number" id="kalkulacija_marza" name="kalkulacija_marza" min="0" max="1000" step="0.01" value="{{.KalkulacijaMarza}}"
|
||||
style="width:140px;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;">
|
||||
</div>
|
||||
<button type="submit"
|
||||
style="padding:9px 18px;background:var(--sb-akcent);border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;">
|
||||
Sačuvaj
|
||||
</button>
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:8px;">
|
||||
Početna marža za formiranje prodajne cene pri nabavci. Po stavci je možeš promeniti.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- panel sa listom backupa -->
|
||||
<div id="backup-panel" style="display:none;margin-top:16px;border-top:0.5px solid var(--ivica);padding-top:16px;">
|
||||
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);margin-bottom:10px;">Dostupne rezervne kopije</div>
|
||||
|
||||
Reference in New Issue
Block a user