Izveštaji: prava zarada (cena - nabavna cena) za prodaju i servis
Nabavna cena se zamrzava na stavci prodaje i ugrađenom servisnom delu u trenutku unosa (migracija 101), umesto naknadnog join-a na trenutnu artikli.nabavna_cena. Tabela mesečnog prihoda sad prikazuje zaradu uz prodaju/servis/ukupno, a grafikon dobija preklopljene tamne barove (zarada) preko svetlih (ukupan prihod), odvojene za prodaju i servis.
This commit is contained in:
@@ -165,6 +165,42 @@ func (r *sqliteIzvestajRepo) MesecniPrihodServis(ctx context.Context) ([]model.M
|
||||
GROUP BY substr(datum_zavrsetka, 1, 7)`, "MesecniPrihodServis")
|
||||
}
|
||||
|
||||
// MesecniZaradaProdaja je zarada prodaje (ukupno − nabavna cena × količina),
|
||||
// nabavna_cena je zamrznuta na stavci u trenutku prodaje (vidi migraciju 101).
|
||||
func (r *sqliteIzvestajRepo) MesecniZaradaProdaja(ctx context.Context) ([]model.MesecniIznos, error) {
|
||||
return r.mesecniPrihod(ctx, `
|
||||
SELECT substr(pn.datum, 1, 7), SUM(sp.ukupno - sp.nabavna_cena * sp.kolicina)
|
||||
FROM prodajni_nalozi pn
|
||||
JOIN stavke_prodaje sp ON sp.nalog_id = pn.id
|
||||
WHERE pn.stornirano = 0
|
||||
AND substr(pn.datum, 1, 10) >= date('now', '-11 months', 'start of month')
|
||||
GROUP BY substr(pn.datum, 1, 7)`, "MesecniZaradaProdaja")
|
||||
}
|
||||
|
||||
// MesecniZaradaServis je zarada servisa: ugrađeni delovi (cena − nabavna cena) +
|
||||
// rad u punom iznosu (servis_radovi nema polje za trošak). Isti filter statusa/
|
||||
// naplate kao MesecniPrihodServis.
|
||||
func (r *sqliteIzvestajRepo) MesecniZaradaServis(ctx context.Context) ([]model.MesecniIznos, error) {
|
||||
return r.mesecniPrihod(ctx, `
|
||||
SELECT mesec, SUM(zarada) FROM (
|
||||
SELECT substr(sn.datum_zavrsetka, 1, 7) AS mesec,
|
||||
(sd.cena_komada - sd.nabavna_cena) * sd.kolicina AS zarada
|
||||
FROM servisni_delovi sd
|
||||
JOIN servisni_nalozi sn ON sn.id = sd.nalog_id
|
||||
WHERE sn.datum_zavrsetka IS NOT NULL AND sn.status = 'Preuzeto'
|
||||
AND (sn.naplaceno > 0 OR sn.avans > 0) AND sd.predlozeno = 0
|
||||
UNION ALL
|
||||
SELECT substr(sn.datum_zavrsetka, 1, 7) AS mesec,
|
||||
sr.cena_komada * sr.kolicina AS zarada
|
||||
FROM servis_radovi sr
|
||||
JOIN servisni_nalozi sn ON sn.id = sr.nalog_id
|
||||
WHERE sn.datum_zavrsetka IS NOT NULL AND sn.status = 'Preuzeto'
|
||||
AND (sn.naplaceno > 0 OR sn.avans > 0)
|
||||
)
|
||||
WHERE mesec >= strftime('%Y-%m', date('now', '-11 months', 'start of month'))
|
||||
GROUP BY mesec`, "MesecniZaradaServis")
|
||||
}
|
||||
|
||||
func (r *sqliteIzvestajRepo) StariOtvoreniNalozi(ctx context.Context) ([]model.StariNalogRed, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT sn.id, sn.broj_naloga, sn.uredjaj, sn.status, sn.datum_prijema,
|
||||
|
||||
@@ -204,9 +204,10 @@ func (r *ProdajaRepo) Kreiraj(ctx context.Context, n *model.ProdajniNalog, stavk
|
||||
s := stavke[i]
|
||||
var naziv, tip string
|
||||
var stanjePre int
|
||||
var nabavnaCena float64
|
||||
err := tx.QueryRowContext(ctx,
|
||||
"SELECT naziv, kolicina, tip FROM artikli WHERE id = ?", s.ArtikalID,
|
||||
).Scan(&naziv, &stanjePre, &tip)
|
||||
"SELECT naziv, kolicina, tip, nabavna_cena FROM artikli WHERE id = ?", s.ArtikalID,
|
||||
).Scan(&naziv, &stanjePre, &tip, &nabavnaCena)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ntech: ProdajaRepo.Kreiraj: dohvati artikal: %w", err)
|
||||
}
|
||||
@@ -248,10 +249,10 @@ func (r *ProdajaRepo) Kreiraj(ctx context.Context, n *model.ProdajniNalog, stavk
|
||||
stavke[i].Ukupno = ukupnoStavke
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO stavke_prodaje
|
||||
(nalog_id, artikal_id, kolicina, cena_po_komadu, popust_procenat, ukupno, pdv_stopa, pdv_iznos, cena_bez_pdv)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
(nalog_id, artikal_id, kolicina, cena_po_komadu, popust_procenat, ukupno, pdv_stopa, pdv_iznos, cena_bez_pdv, nabavna_cena)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
nalogID, s.ArtikalID, s.Kolicina, s.CenaPoKomadu, s.PopustProcenat, ukupnoStavke,
|
||||
s.PdvStopa, pdvIznos, cenaBezPdv,
|
||||
s.PdvStopa, pdvIznos, cenaBezPdv, nabavnaCena,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ntech: ProdajaRepo.Kreiraj: insert stavka: %w", err)
|
||||
|
||||
@@ -95,9 +95,10 @@ func ugradiIliPotrazujTx(ctx context.Context, tx *sql.Tx, nalogID, artikalID int
|
||||
|
||||
// Ugrađeni delovi: standardna logika — skidamo sa lagera
|
||||
var stanjePre int
|
||||
var nabavnaCena float64
|
||||
if err = tx.QueryRowContext(ctx,
|
||||
"SELECT kolicina FROM artikli WHERE id = ?", artikalID,
|
||||
).Scan(&stanjePre); err != nil {
|
||||
"SELECT kolicina, nabavna_cena FROM artikli WHERE id = ?", artikalID,
|
||||
).Scan(&stanjePre, &nabavnaCena); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: dohvati stanje: %w", err)
|
||||
}
|
||||
|
||||
@@ -119,15 +120,15 @@ func ugradiIliPotrazujTx(ctx context.Context, tx *sql.Tx, nalogID, artikalID int
|
||||
).Scan(&postojeciID, &postojeciKol)
|
||||
if errRed == nil {
|
||||
if _, err = tx.ExecContext(ctx,
|
||||
"UPDATE servisni_delovi SET kolicina = ?, cena_komada = ? WHERE id = ?",
|
||||
postojeciKol+ugradjeno, cenaKomada, postojeciID,
|
||||
"UPDATE servisni_delovi SET kolicina = ?, cena_komada = ?, nabavna_cena = ? WHERE id = ?",
|
||||
postojeciKol+ugradjeno, cenaKomada, nabavnaCena, postojeciID,
|
||||
); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: merge: %w", err)
|
||||
}
|
||||
} else if errors.Is(errRed, sql.ErrNoRows) {
|
||||
if _, err = tx.ExecContext(ctx,
|
||||
"INSERT INTO servisni_delovi (nalog_id, artikal_id, kolicina, cena_komada, predlozeno) VALUES (?, ?, ?, ?, 0)",
|
||||
nalogID, artikalID, ugradjeno, cenaKomada,
|
||||
"INSERT INTO servisni_delovi (nalog_id, artikal_id, kolicina, cena_komada, predlozeno, nabavna_cena) VALUES (?, ?, ?, ?, 0, ?)",
|
||||
nalogID, artikalID, ugradjeno, cenaKomada, nabavnaCena,
|
||||
); err != nil {
|
||||
return 0, 0, fmt.Errorf("ntech: ugradiIliPotrazujTx: insert: %w", err)
|
||||
}
|
||||
|
||||
@@ -196,6 +196,13 @@ func (r *ServisniPotrazivaniDeloviRepo) ProveriIPocistiZaArtikal(ctx context.Con
|
||||
// uvećava postojeći red za isti artikal na nalogu ili kreira novi. Radi unutar
|
||||
// prosleđene transakcije. Ne dira magacin — to radi skiniSaMagacina zasebno.
|
||||
func (r *ServisniPotrazivaniDeloviRepo) ugradiUNalog(ctx context.Context, tx *sql.Tx, nalogID, artikalID int64, kolicina int, cenaKomada float64) error {
|
||||
var nabavnaCena float64
|
||||
if err := tx.QueryRowContext(ctx,
|
||||
"SELECT nabavna_cena FROM artikli WHERE id = ?", artikalID,
|
||||
).Scan(&nabavnaCena); err != nil {
|
||||
return fmt.Errorf("ntech: ServisniPotrazivaniDeloviRepo.ugradiUNalog: nabavna cena: %w", err)
|
||||
}
|
||||
|
||||
var postojeciID int64
|
||||
var postojeciKol int
|
||||
err := tx.QueryRowContext(ctx,
|
||||
@@ -207,8 +214,8 @@ func (r *ServisniPotrazivaniDeloviRepo) ugradiUNalog(ctx context.Context, tx *sq
|
||||
// ne pregazi postojeću cenu nulom (npr. legacy potraživani red bez cene)
|
||||
if cenaKomada > 0 {
|
||||
_, err = tx.ExecContext(ctx,
|
||||
"UPDATE servisni_delovi SET kolicina = ?, cena_komada = ? WHERE id = ?",
|
||||
postojeciKol+kolicina, cenaKomada, postojeciID,
|
||||
"UPDATE servisni_delovi SET kolicina = ?, cena_komada = ?, nabavna_cena = ? WHERE id = ?",
|
||||
postojeciKol+kolicina, cenaKomada, nabavnaCena, postojeciID,
|
||||
)
|
||||
} else {
|
||||
_, err = tx.ExecContext(ctx,
|
||||
@@ -223,9 +230,9 @@ func (r *ServisniPotrazivaniDeloviRepo) ugradiUNalog(ctx context.Context, tx *sq
|
||||
}
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO servisni_delovi (nalog_id, artikal_id, kolicina, cena_komada)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
nalogID, artikalID, kolicina, cenaKomada,
|
||||
INSERT INTO servisni_delovi (nalog_id, artikal_id, kolicina, cena_komada, nabavna_cena)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
nalogID, artikalID, kolicina, cenaKomada, nabavnaCena,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ntech: ServisniPotrazivaniDeloviRepo.ugradiUNalog: insert: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user