feat(pricing): kit/pachet pricing with price list lookup, replace procent_pret

- Oracle PL/SQL: kit pricing logic with Mode A (distributed discount) and
  Mode B (separate discount line), dual policy support, PRETURI_CU_TVA flag
- Eliminate procent_pret from entire stack (Oracle, Python, JS, HTML)
- New settings: kit_pricing_mode, kit_discount_codmat, price_sync_enabled
- Settings UI: cards for Kit Pricing and Price Sync configuration
- Mappings UI: kit badges with lazy-loaded component prices from price list
- Price sync from orders: auto-update ROA prices when web prices differ
- Catalog price sync: new service to sync all GoMag product prices to ROA
- Kit component price validation: pre-check prices before import
- New endpoint GET /api/mappings/{sku}/prices for component price display
- New endpoints POST /api/price-sync/start, GET status, GET history
- DDL script 07_drop_procent_pret.sql (run after deploy confirmation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-19 22:29:18 +00:00
parent bedb93affe
commit 9e5901a8fb
17 changed files with 1313 additions and 268 deletions

View File

@@ -47,14 +47,6 @@
</div>
</div>
<!-- Percentage filter pills -->
<div class="filter-bar" id="mappingsFilterBar">
<button class="filter-pill active d-none d-md-inline-flex" data-pct="all">Toate <span class="filter-count fc-neutral" id="mCntAll">0</span></button>
<button class="filter-pill d-none d-md-inline-flex" data-pct="complete">Complete <span class="filter-count fc-green" id="mCntComplete">0</span></button>
<button class="filter-pill d-none d-md-inline-flex" data-pct="incomplete">Incomplete <span class="filter-count fc-yellow" id="mCntIncomplete">0</span></button>
</div>
<div class="d-md-none mb-2" id="mappingsMobileSeg"></div>
<!-- Top pagination -->
<div id="mappingsPagTop" class="pag-strip"></div>
@@ -110,7 +102,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-muted small">Format CSV: sku, codmat, cantitate_roa, procent_pret</p>
<p class="text-muted small">Format CSV: sku, codmat, cantitate_roa</p>
<input type="file" class="form-control" id="csvFile" accept=".csv">
<div id="importResult" class="mt-3"></div>
</div>
@@ -154,5 +146,5 @@
{% endblock %}
{% block scripts %}
<script src="{{ request.scope.get('root_path', '') }}/static/js/mappings.js?v=7"></script>
<script src="{{ request.scope.get('root_path', '') }}/static/js/mappings.js?v=9"></script>
{% endblock %}

View File

@@ -157,6 +157,72 @@
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header py-2 px-3 fw-semibold">Pricing Kituri / Pachete</div>
<div class="card-body py-2 px-3">
<div class="mb-2">
<div class="form-check">
<input class="form-check-input" type="radio" name="kitPricingMode" id="kitModeOff" value="" checked>
<label class="form-check-label small" for="kitModeOff">Dezactivat</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="kitPricingMode" id="kitModeDistributed" value="distributed">
<label class="form-check-label small" for="kitModeDistributed">Distribuire discount în preț</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="kitPricingMode" id="kitModeSeparate" value="separate_line">
<label class="form-check-label small" for="kitModeSeparate">Linie discount separată</label>
</div>
</div>
<div id="kitModeBFields" style="display:none">
<div class="mb-2">
<label class="form-label mb-0 small">Kit Discount CODMAT</label>
<div class="position-relative">
<input type="text" class="form-control form-control-sm" id="settKitDiscountCodmat" placeholder="ex: DISCOUNT_KIT" autocomplete="off">
<div class="autocomplete-dropdown d-none" id="settKitDiscountAc"></div>
</div>
</div>
<div class="mb-2">
<label class="form-label mb-0 small">Kit Discount Politică</label>
<select class="form-select form-select-sm" id="settKitDiscountIdPol">
<option value="">— implicită —</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header py-2 px-3 fw-semibold">Sincronizare Prețuri</div>
<div class="card-body py-2 px-3">
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input" id="settPriceSyncEnabled" checked>
<label class="form-check-label small" for="settPriceSyncEnabled">Sync automat prețuri din comenzi</label>
</div>
<div class="form-check mb-2">
<input type="checkbox" class="form-check-input" id="settCatalogSyncEnabled">
<label class="form-check-label small" for="settCatalogSyncEnabled">Sync prețuri din catalog GoMag</label>
</div>
<div id="catalogSyncOptions" style="display:none">
<div class="mb-2">
<label class="form-label mb-0 small">Program</label>
<select class="form-select form-select-sm" id="settPriceSyncSchedule">
<option value="">Doar manual</option>
<option value="daily_03:00">Zilnic la 03:00</option>
<option value="daily_06:00">Zilnic la 06:00</option>
</select>
</div>
</div>
<div id="settPriceSyncStatus" class="text-muted small mt-2"></div>
<button class="btn btn-sm btn-outline-primary mt-2" id="btnCatalogSync" onclick="startCatalogSync()">Sincronizează acum</button>
</div>
</div>
</div>
</div>
<div class="mb-3">
@@ -167,5 +233,5 @@
{% endblock %}
{% block scripts %}
<script src="{{ request.scope.get('root_path', '') }}/static/js/settings.js?v=6"></script>
<script src="{{ request.scope.get('root_path', '') }}/static/js/settings.js?v=7"></script>
{% endblock %}