feat: show prices for all mappings + remove VAT% display

Join price policies directly into get_mappings() query so single-article
mappings display prices without extra API calls. Remove VAT percentage
from kit price display.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-20 23:15:26 +00:00
parent 5addeb08bd
commit 10c1afca01
4 changed files with 33 additions and 9 deletions

View File

@@ -61,9 +61,14 @@ async def mappings_page(request: Request):
async def list_mappings(search: str = "", page: int = 1, per_page: int = 50,
sort_by: str = "sku", sort_dir: str = "asc",
show_deleted: bool = False):
app_settings = await sqlite_service.get_app_settings()
id_pol = int(app_settings.get("id_pol") or 0) or None
id_pol_productie = int(app_settings.get("id_pol_productie") or 0) or None
result = mapping_service.get_mappings(search=search, page=page, per_page=per_page,
sort_by=sort_by, sort_dir=sort_dir,
show_deleted=show_deleted)
show_deleted=show_deleted,
id_pol=id_pol, id_pol_productie=id_pol_productie)
# Merge product names from web_products (R4)
skus = list({m["sku"] for m in result.get("mappings", [])})
product_names = await sqlite_service.get_web_products_batch(skus)

View File

@@ -9,7 +9,8 @@ logger = logging.getLogger(__name__)
def get_mappings(search: str = "", page: int = 1, per_page: int = 50,
sort_by: str = "sku", sort_dir: str = "asc",
show_deleted: bool = False):
show_deleted: bool = False,
id_pol: int = None, id_pol_productie: int = None):
"""Get paginated mappings with optional search and sorting."""
if database.pool is None:
raise HTTPException(status_code=503, detail="Oracle unavailable")
@@ -48,13 +49,28 @@ def get_mappings(search: str = "", page: int = 1, per_page: int = 50,
params["search"] = search
where = "WHERE " + " AND ".join(where_clauses) if where_clauses else ""
# Add price policy params
params["id_pol"] = id_pol
params["id_pol_prod"] = id_pol_productie
# Fetch ALL matching rows (no pagination yet — we need to group by SKU first)
data_sql = f"""
SELECT at.sku, at.codmat, na.denumire, na.um, at.cantitate_roa,
at.activ, at.sters,
TO_CHAR(at.data_creare, 'YYYY-MM-DD HH24:MI') as data_creare
TO_CHAR(at.data_creare, 'YYYY-MM-DD HH24:MI') as data_creare,
ROUND(CASE WHEN pp.preturi_cu_tva = 1
THEN NVL(ppa.pret, 0)
ELSE NVL(ppa.pret, 0) * NVL(ppa.proc_tvav, 1.19)
END, 2) AS pret_cu_tva
FROM ARTICOLE_TERTI at
LEFT JOIN nom_articole na ON na.codmat = at.codmat
LEFT JOIN crm_politici_pret_art ppa
ON ppa.id_articol = na.id_articol
AND ppa.id_pol = CASE
WHEN TRIM(na.cont) IN ('341','345') AND :id_pol_prod IS NOT NULL
THEN :id_pol_prod ELSE :id_pol END
LEFT JOIN crm_politici_preturi pp
ON pp.id_pol = ppa.id_pol
{where}
ORDER BY {order_clause}
"""
@@ -351,8 +367,10 @@ def get_component_prices(sku: str, id_pol: int, id_pol_productie: int = None) ->
""", {"sku": sku})
components = cur.fetchall()
if len(components) <= 1:
return [] # Not a kit
if len(components) == 0:
return []
if len(components) == 1 and (components[0][1] or 1) <= 1:
return [] # True 1:1 mapping, no kit pricing needed
result = []
for codmat, cant_roa, id_art, cont, denumire in components:

View File

@@ -121,13 +121,14 @@ function renderTable(mappings, showDeleted) {
}
const deletedStyle = m.sters ? 'text-decoration:line-through;opacity:0.5;' : '';
const isKitRow = (skuCodmatCount[m.sku] || 0) > 1;
const priceSlot = isKitRow ? `<span class="kit-price-slot text-muted small ms-2" data-sku="${esc(m.sku)}" data-codmat="${esc(m.codmat)}"></span>` : '';
const kitPriceSlot = isKitRow ? `<span class="kit-price-slot text-muted small ms-2" data-sku="${esc(m.sku)}" data-codmat="${esc(m.codmat)}"></span>` : '';
const inlinePrice = m.pret_cu_tva ? `<span class="text-muted small ms-2">${parseFloat(m.pret_cu_tva).toFixed(2)} lei</span>` : '';
html += `<div class="flat-row" style="padding-left:1.5rem;font-size:0.9rem;${deletedStyle}">
<code>${esc(m.codmat)}</code>
<span class="grow truncate text-muted" style="font-size:0.85rem">${esc(m.denumire || '')}</span>
<span class="text-nowrap" style="font-size:0.875rem">
<span class="${m.sters ? '' : 'editable'}" style="cursor:${m.sters ? 'default' : 'pointer'}"
${m.sters ? '' : `onclick="editFlatValue(this, '${esc(m.sku)}', '${esc(m.codmat)}', 'cantitate_roa', ${m.cantitate_roa})"`}>x${m.cantitate_roa}</span>${priceSlot}
${m.sters ? '' : `onclick="editFlatValue(this, '${esc(m.sku)}', '${esc(m.codmat)}', 'cantitate_roa', ${m.cantitate_roa})"`}>x${m.cantitate_roa}</span>${isKitRow ? kitPriceSlot : inlinePrice}
</span>
</div>`;
@@ -197,7 +198,7 @@ function renderKitPrices(sku, prices, container) {
const codmat = slot.dataset.codmat;
const p = prices.find(pr => pr.codmat === codmat);
if (p && p.pret_cu_tva > 0) {
slot.innerHTML = `${p.pret_cu_tva.toFixed(2)} lei (${p.ptva}%)`;
slot.innerHTML = `${p.pret_cu_tva.toFixed(2)} lei`;
total += p.pret_cu_tva * (p.cantitate_roa || 1);
} else if (p) {
slot.innerHTML = `<span class="text-muted">fără preț</span>`;

View File

@@ -150,5 +150,5 @@
{% endblock %}
{% block scripts %}
<script src="{{ request.scope.get('root_path', '') }}/static/js/mappings.js?v=10"></script>
<script src="{{ request.scope.get('root_path', '') }}/static/js/mappings.js?v=11"></script>
{% endblock %}