fix(mappings): move sku/codmat from path to query — slash in keys 404'd
Codurile ROA contin legitim '/' si '"' (ex. RCR1/4"). Rutele FastAPI
foloseau parametri de cale (/api/mappings/{sku}/{codmat}...); ASGI decodeaza
%2F -> '/' INAINTE de routing, deci o cheie cu slash devine doua segmente si
Starlette nu mai potriveste ruta -> 404 la edit/delete/restore. Cand ambii
parametri au slash, un convertor {path} nu rezolva (split ambiguu).
Solutie: sku/codmat trec din cale in query string. %2F/%22 din query se
decodeaza ca parte a valorii, fara sa atinga routing-ul de cale.
- backend: 4 rute statice + Query(...) (update/edit/delete/restore);
semnaturile mapping_service raman neschimbate
- frontend: helper mapQS(); toate cele 8 apeluri path -> query; cache-bust ?v=19
- tests: regresie ci-layer (chei-sentinel cu slash, !=404, toate 4 rutele);
test_integration update/delete convertite la query; e2e delete RCR1/4"
(slash+ghilimea) prin modalul UI, skip cand Oracle lipseste
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,16 +95,18 @@ async def create_mapping(data: MappingCreate):
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@router.put("/api/mappings/{sku}/{codmat}")
|
||||
def update_mapping(sku: str, codmat: str, data: MappingUpdate):
|
||||
# sku/codmat in query string (not path) — codurile ROA contin legitim '/' si '"'
|
||||
# (ex. RCR1/4"); %2F in path e decodat de ASGI inainte de routing -> 404.
|
||||
@router.put("/api/mappings/update")
|
||||
def update_mapping(data: MappingUpdate, sku: str = Query(...), codmat: str = Query(...)):
|
||||
try:
|
||||
updated = mapping_service.update_mapping(sku, codmat, data.cantitate_roa, data.activ)
|
||||
return {"success": updated}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@router.put("/api/mappings/{sku}/{codmat}/edit")
|
||||
def edit_mapping(sku: str, codmat: str, data: MappingEdit):
|
||||
@router.put("/api/mappings/edit")
|
||||
def edit_mapping(data: MappingEdit, sku: str = Query(...), codmat: str = Query(...)):
|
||||
try:
|
||||
result = mapping_service.edit_mapping(sku, codmat, data.new_sku, data.new_codmat,
|
||||
data.cantitate_roa)
|
||||
@@ -112,16 +114,16 @@ def edit_mapping(sku: str, codmat: str, data: MappingEdit):
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@router.delete("/api/mappings/{sku}/{codmat}")
|
||||
def delete_mapping(sku: str, codmat: str):
|
||||
@router.delete("/api/mappings/delete")
|
||||
def delete_mapping(sku: str = Query(...), codmat: str = Query(...)):
|
||||
try:
|
||||
deleted = mapping_service.delete_mapping(sku, codmat)
|
||||
return {"success": deleted}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@router.post("/api/mappings/{sku}/{codmat}/restore")
|
||||
def restore_mapping(sku: str, codmat: str):
|
||||
@router.post("/api/mappings/restore")
|
||||
def restore_mapping(sku: str = Query(...), codmat: str = Query(...)):
|
||||
try:
|
||||
restored = mapping_service.restore_mapping(sku, codmat)
|
||||
return {"success": restored}
|
||||
|
||||
@@ -8,6 +8,12 @@ let editingMapping = null; // {sku, codmat} when editing
|
||||
|
||||
const kitPriceCache = new Map();
|
||||
|
||||
// sku/codmat in query string — codurile ROA contin legitim '/' si '"' (ex. RCR1/4");
|
||||
// %2F in path e decodat de ASGI inainte de routing -> 404.
|
||||
function mapQS(sku, codmat) {
|
||||
return `sku=${encodeURIComponent(sku)}&codmat=${encodeURIComponent(codmat)}`;
|
||||
}
|
||||
|
||||
// Load on page ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadMappings();
|
||||
@@ -242,7 +248,7 @@ function editFlatValue(span, sku, codmat, field, currentValue) {
|
||||
try {
|
||||
const body = {};
|
||||
body[field] = newValue;
|
||||
const res = await fetch(`/api/mappings/${encodeURIComponent(sku)}/${encodeURIComponent(codmat)}`, {
|
||||
const res = await fetch(`/api/mappings/update?${mapQS(sku, codmat)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
@@ -432,7 +438,7 @@ async function saveMapping() {
|
||||
if (editingMapping) {
|
||||
if (mappings.length === 1) {
|
||||
// Single CODMAT edit: use existing PUT endpoint
|
||||
res = await fetch(`/api/mappings/${encodeURIComponent(editingMapping.sku)}/${encodeURIComponent(editingMapping.codmat)}/edit`, {
|
||||
res = await fetch(`/api/mappings/edit?${mapQS(editingMapping.sku, editingMapping.codmat)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -450,7 +456,7 @@ async function saveMapping() {
|
||||
|
||||
// Delete each existing CODMAT for old SKU
|
||||
for (const m of existing) {
|
||||
await fetch(`/api/mappings/${encodeURIComponent(m.sku)}/${encodeURIComponent(m.codmat)}`, {
|
||||
await fetch(`/api/mappings/delete?${mapQS(m.sku, m.codmat)}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
}
|
||||
@@ -588,7 +594,7 @@ function cancelInlineAdd() {
|
||||
async function toggleActive(sku, codmat, currentActive) {
|
||||
const newActive = currentActive ? 0 : 1;
|
||||
try {
|
||||
const res = await fetch(`/api/mappings/${encodeURIComponent(sku)}/${encodeURIComponent(codmat)}`, {
|
||||
const res = await fetch(`/api/mappings/update?${mapQS(sku, codmat)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ activ: newActive })
|
||||
@@ -601,7 +607,7 @@ async function toggleActive(sku, codmat, currentActive) {
|
||||
// Show toast with undo
|
||||
const action = newActive ? 'activata' : 'dezactivata';
|
||||
showUndoToast(`Mapare ${sku} \u2192 ${codmat} ${action}.`, () => {
|
||||
fetch(`/api/mappings/${encodeURIComponent(sku)}/${encodeURIComponent(codmat)}`, {
|
||||
fetch(`/api/mappings/update?${mapQS(sku, codmat)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ activ: currentActive })
|
||||
@@ -643,7 +649,7 @@ function initDeleteModal() {
|
||||
if (!pendingDelete) return;
|
||||
const { sku, codmat } = pendingDelete;
|
||||
try {
|
||||
const res = await fetch(`/api/mappings/${encodeURIComponent(sku)}/${encodeURIComponent(codmat)}`, {
|
||||
const res = await fetch(`/api/mappings/delete?${mapQS(sku, codmat)}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -669,7 +675,7 @@ function deleteMappingConfirm(sku, codmat) {
|
||||
|
||||
async function restoreMapping(sku, codmat) {
|
||||
try {
|
||||
const res = await fetch(`/api/mappings/${encodeURIComponent(sku)}/${encodeURIComponent(codmat)}/restore`, {
|
||||
const res = await fetch(`/api/mappings/restore?${mapQS(sku, codmat)}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -721,7 +727,7 @@ function handleMappingConflict(data) {
|
||||
const sku = (document.getElementById('inlineSku') || document.getElementById('inputSku'))?.value?.trim();
|
||||
const codmat = (document.getElementById('inlineCodmat') || document.querySelector('.cl-codmat'))?.value?.trim();
|
||||
if (sku && codmat) {
|
||||
fetch(`/api/mappings/${encodeURIComponent(sku)}/${encodeURIComponent(codmat)}/restore`, { method: 'POST' })
|
||||
fetch(`/api/mappings/restore?${mapQS(sku, codmat)}`, { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (d.success) { cancelInlineAdd(); loadMappings(); }
|
||||
|
||||
@@ -159,5 +159,5 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/mappings.js?v=18"></script>
|
||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/mappings.js?v=19"></script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user