Raport stocuri marfa 371 pe vechimi (dosar credit banca)
Sectiune dedicata pentru banca cu sumar pe grupe x bucket vechime (0-6 luni, 6-12 luni, 1-2 ani, 2-3 ani, >3 ani), detaliu articole si reconciliere contra sold contabil 371 din balanta (vbal). - queries.py: STOCURI_371_SUMAR (ROLLUP grupa/subgrupa), STOCURI_371_DETALIU, STOCURI_371_SOLD_CONTABIL (sold sintetic din vbal, an/luna snapshot). Filtru stoc (cants+cant-cante) <> 0 pentru acoperire cu soldul contabil. - main.py: CLI --aging-dates pentru evolutie multi-data a stocului >3 ani, pagina PDF dedicata cu nota metodologica + reconciliere (marker la diff >1%). - recommendations.py: alerta CONCENTRARE cand stoc 371 >3 ani depaseste prag. - config.py: threshold aged_stock_371_pct (default 15%). - run.bat: header documentar cu argumentele disponibile + exemple. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,7 @@ RECOMMENDATION_THRESHOLDS = {
|
||||
# Stock indicators
|
||||
'stoc_zile_max': 90, # days - slow stock threshold
|
||||
'rotatie_minima': 4, # minimum annual rotation
|
||||
'aged_stock_371_pct': 0.15, # prag CONCENTRARE pentru stoc 371 >3 ani
|
||||
|
||||
# Receivables aging
|
||||
'restante_90_procent': 10, # % - max receivables > 90 days
|
||||
|
||||
204
main.py
204
main.py
@@ -280,6 +280,25 @@ class PerformanceLogger:
|
||||
print(f"\n📝 Log saved to: {log_file}")
|
||||
|
||||
|
||||
def parse_aging_dates(raw: str) -> list:
|
||||
"""Parse --aging-dates CLI value into sorted list of data_referinta datetimes.
|
||||
|
||||
Accepts 'YYYY-MM,YYYY-MM,...' (end-of-month), returns list of first-day-next-month datetimes.
|
||||
Raises SystemExit on any invalid token so user sees a clear message, not a crash.
|
||||
"""
|
||||
dates = []
|
||||
for token in raw.split(','):
|
||||
tok = token.strip()
|
||||
if not tok:
|
||||
continue
|
||||
try:
|
||||
dates.append(compute_data_referinta(tok))
|
||||
except ValueError as e:
|
||||
print(f"❌ --aging-dates: token invalid '{tok}' (astept YYYY-MM). {e}")
|
||||
sys.exit(1)
|
||||
return sorted(set(dates))
|
||||
|
||||
|
||||
def compute_data_referinta(end_month_str: str = None) -> datetime:
|
||||
"""
|
||||
Compute reference date (first day of month AFTER the last reporting month).
|
||||
@@ -389,6 +408,11 @@ def generate_reports(args):
|
||||
results = {}
|
||||
perf = PerformanceLogger() # Initialize performance logger
|
||||
|
||||
# Parse multi-date aging request (validation before DB connection)
|
||||
aging_dates_list = []
|
||||
if getattr(args, 'aging_dates', None):
|
||||
aging_dates_list = parse_aging_dates(args.aging_dates)
|
||||
|
||||
with OracleConnection() as conn:
|
||||
print("\n📥 Extragere date din Oracle:\n")
|
||||
|
||||
@@ -398,6 +422,37 @@ def generate_reports(args):
|
||||
results[query_name] = df
|
||||
perf.stop(rows=len(df) if df is not None and not df.empty else 0)
|
||||
|
||||
# --- Evolutie multi-data stoc 371 >3 ani (optional) ---
|
||||
if aging_dates_list:
|
||||
print(f"\n📥 Extragere evolutie stoc 371 pe {len(aging_dates_list)} date:")
|
||||
evolutie_rows = []
|
||||
sumar_info = QUERIES['stocuri_371_sumar']
|
||||
original_param = sumar_info['params'].get('data_referinta')
|
||||
for dref in aging_dates_list:
|
||||
perf.start(f"QUERY: stocuri_371_sumar@{dref.strftime('%Y-%m-%d')}")
|
||||
sumar_info['params']['data_referinta'] = dref
|
||||
df_e = execute_query(conn, f"stocuri_371_sumar@{dref.strftime('%Y-%m')}", sumar_info)
|
||||
perf.stop(rows=len(df_e) if df_e is not None else 0)
|
||||
if df_e is None or df_e.empty:
|
||||
continue
|
||||
grand = df_e[df_e['GROUPING_LEVEL'] == 3]
|
||||
if grand.empty:
|
||||
continue
|
||||
row = grand.iloc[0]
|
||||
val_total = float(row.get('VALOARE_TOTAL', 0) or 0)
|
||||
val_3 = float(row.get('VAL_PESTE_3_ANI', 0) or 0)
|
||||
evolutie_rows.append({
|
||||
'DATA_REFERINTA': (dref - timedelta(days=1)).strftime('%Y-%m-%d'),
|
||||
'VALOARE_TOTAL': round(val_total, 2),
|
||||
'VALOARE_PESTE_3_ANI': round(val_3, 2),
|
||||
'PROCENT_PESTE_3_ANI': round(val_3 / val_total * 100, 2) if val_total > 0 else 0.0
|
||||
})
|
||||
# restore original data_referinta for downstream consumers
|
||||
sumar_info['params']['data_referinta'] = original_param
|
||||
results['stocuri_371_evolutie'] = pd.DataFrame(evolutie_rows)
|
||||
else:
|
||||
results['stocuri_371_evolutie'] = pd.DataFrame()
|
||||
|
||||
# Generate Excel Report
|
||||
print("\n📝 Generare raport Excel...")
|
||||
excel_gen = ExcelReportGenerator(excel_path)
|
||||
@@ -537,6 +592,9 @@ def generate_reports(args):
|
||||
# STOC
|
||||
'stoc_curent',
|
||||
'stoc_lent',
|
||||
'stocuri_371_sumar',
|
||||
'stocuri_371_detaliu',
|
||||
'stocuri_371_evolutie',
|
||||
'rotatie_stocuri',
|
||||
|
||||
# PRODUCTIE
|
||||
@@ -576,6 +634,28 @@ def generate_reports(args):
|
||||
'VALOARE': 'Cantitate × preț achiziție',
|
||||
'ZILE_FARA_MISCARE': 'Zile de la ultima ieșire (dataout) sau intrare'
|
||||
},
|
||||
'stocuri_371_sumar': {
|
||||
'GROUPING_LEVEL': '0 = detaliu subgrupa, 1 = subtotal per grupa, 3 = grand total (ROLLUP)',
|
||||
'VALOARE_TOTAL': 'Total valoare stoc cont 371 la data de referinta (cost achizitie fara TVA)',
|
||||
'VAL_0_6_LUNI': 'Valoare articole cu vechime 0-180 zile',
|
||||
'VAL_6_12_LUNI': 'Valoare articole cu vechime 181-365 zile',
|
||||
'VAL_1_2_ANI': 'Valoare articole cu vechime 366-730 zile',
|
||||
'VAL_2_3_ANI': 'Valoare articole cu vechime 731-1095 zile',
|
||||
'VAL_PESTE_3_ANI': 'Valoare articole cu vechime peste 1095 zile (risc haircut bancar)'
|
||||
},
|
||||
'stocuri_371_detaliu': {
|
||||
'CANTITATE': 'Stoc final = cants + cant - cante',
|
||||
'VALOARE': 'Cantitate × pret achizitie (cost, fara TVA)',
|
||||
'ZILE_VECHIME': '(data_referinta - 1) - NVL(dataout, datain)',
|
||||
'ANI_VECHIME': 'zile_vechime / 365',
|
||||
'BUCKET_VECHIME': 'Grupare: 0-6 luni / 6-12 luni / 1-2 ani / 2-3 ani / >3 ani'
|
||||
},
|
||||
'stocuri_371_evolutie': {
|
||||
'DATA_REFERINTA': 'End-of-month pentru care s-a calculat snapshot-ul',
|
||||
'VALOARE_TOTAL': 'Total stoc 371 la acea data',
|
||||
'VALOARE_PESTE_3_ANI': 'Portiunea cu vechime >3 ani (trend an-la-an pentru banca)',
|
||||
'PROCENT_PESTE_3_ANI': '% din total care depaseste 3 ani'
|
||||
},
|
||||
'rotatie_stocuri': {
|
||||
'VALOARE_STOC': 'Stoc curent (cants+cant-cante) × preț achiziție',
|
||||
'VANZARI_12_LUNI': 'Doar vânzări (nu transferuri/consumuri) din ultimele 12 luni',
|
||||
@@ -916,24 +996,37 @@ def generate_reports(args):
|
||||
'grad_acoperire_datorii', 'proiectie_lichiditate'
|
||||
}
|
||||
|
||||
# Metadata pentru sheet-uri sintetice (nu sunt in QUERIES dar sunt randate)
|
||||
synthetic_sheets = {
|
||||
'stocuri_371_evolutie': {
|
||||
'title': 'Stocuri Marfa 371 - Evolutie >3 ani',
|
||||
'description': 'Valoare totala 371 si portiune >3 ani la mai multe end-of-month (trend)'
|
||||
}
|
||||
}
|
||||
|
||||
for query_name in sheet_order:
|
||||
# Skip consolidated sheets and their source sheets
|
||||
if query_name in consolidated_sheets:
|
||||
continue
|
||||
|
||||
if query_name in results and query_name in QUERIES:
|
||||
query_info = QUERIES[query_name]
|
||||
# Create short sheet name from query name
|
||||
if query_name in results and (query_name in QUERIES or query_name in synthetic_sheets):
|
||||
df_sheet = results[query_name]
|
||||
if df_sheet is None or df_sheet.empty:
|
||||
continue
|
||||
if query_name in QUERIES:
|
||||
meta = QUERIES[query_name]
|
||||
else:
|
||||
meta = synthetic_sheets[query_name]
|
||||
sheet_name = query_name.replace('_', ' ').title()[:31]
|
||||
perf.start(f"EXCEL: {query_name} detail sheet")
|
||||
excel_gen.add_sheet(
|
||||
name=sheet_name,
|
||||
df=results[query_name],
|
||||
title=query_info.get('title', query_name),
|
||||
description=query_info.get('description', ''),
|
||||
df=df_sheet,
|
||||
title=meta.get('title', query_name),
|
||||
description=meta.get('description', ''),
|
||||
legend=legends.get(query_name)
|
||||
)
|
||||
df_rows = len(results[query_name]) if results[query_name] is not None else 0
|
||||
df_rows = len(df_sheet)
|
||||
perf.stop(rows=df_rows)
|
||||
|
||||
perf.start("EXCEL: Save workbook")
|
||||
@@ -1087,6 +1180,94 @@ def generate_reports(args):
|
||||
max_rows=20
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# Pagina dedicata BANCA: Stocuri Marfa 371 pe Vechimi + reconciliere
|
||||
# =========================================================================
|
||||
df_sumar_371 = results.get('stocuri_371_sumar')
|
||||
if df_sumar_371 is not None and not df_sumar_371.empty:
|
||||
perf.start("PDF: Stocuri 371 bank page")
|
||||
pdf_gen.add_page_break()
|
||||
|
||||
nota_metodologica = (
|
||||
"METODOLOGIE: Vechimea este calculata ca numar de zile intre data de referinta "
|
||||
f"({end_date.strftime('%d.%m.%Y')}) si ultima miscare a articolului in stoc "
|
||||
"(iesire, sau intrare daca nu a existat nicio iesire). Valoarea este costul de "
|
||||
"achizitie fara TVA (cant × pret din vstoc). Se includ toate pozitiile cu stoc "
|
||||
"nenul (<> 0), inclusiv cele negative (corectii / erori de inventar), ca sa se "
|
||||
"reconcilieze corect cu soldul contabil 371 din balanta (vbal, cont sintetic)."
|
||||
)
|
||||
pdf_gen.add_explanation(nota_metodologica)
|
||||
|
||||
pdf_gen.add_table_section(
|
||||
"Raport Stocuri Marfa 371 - Sumar Vechimi (pentru banca)",
|
||||
df_sumar_371,
|
||||
columns=['GRUPA', 'SUBGRUPA', 'VALOARE_TOTAL', 'VAL_0_6_LUNI',
|
||||
'VAL_6_12_LUNI', 'VAL_1_2_ANI', 'VAL_2_3_ANI', 'VAL_PESTE_3_ANI'],
|
||||
max_rows=30
|
||||
)
|
||||
|
||||
df_evolutie_371 = results.get('stocuri_371_evolutie')
|
||||
if df_evolutie_371 is not None and not df_evolutie_371.empty:
|
||||
pdf_gen.add_table_section(
|
||||
"Evolutie stoc 371 >3 ani (multi-data)",
|
||||
df_evolutie_371,
|
||||
columns=['DATA_REFERINTA', 'VALOARE_TOTAL', 'VALOARE_PESTE_3_ANI', 'PROCENT_PESTE_3_ANI'],
|
||||
max_rows=20
|
||||
)
|
||||
|
||||
df_detaliu_371 = results.get('stocuri_371_detaliu')
|
||||
if df_detaliu_371 is not None and not df_detaliu_371.empty:
|
||||
pdf_gen.add_table_section(
|
||||
"Top 25 articole dupa vechime (detaliu 371)",
|
||||
df_detaliu_371,
|
||||
columns=['COD_ARTICOL', 'DENUMIRE', 'CANTITATE', 'VALOARE',
|
||||
'ZILE_VECHIME', 'BUCKET_VECHIME'],
|
||||
max_rows=25
|
||||
)
|
||||
|
||||
# Reconciliere contabila
|
||||
df_sold = results.get('stocuri_371_sold_contabil')
|
||||
grand_total_row = df_sumar_371[df_sumar_371['GROUPING_LEVEL'] == 3]
|
||||
total_raport = float(grand_total_row['VALOARE_TOTAL'].iloc[0]) if not grand_total_row.empty else 0.0
|
||||
|
||||
sold_contabil = None
|
||||
if df_sold is not None and not df_sold.empty:
|
||||
val = df_sold['SOLD_371'].iloc[0]
|
||||
if val is not None:
|
||||
sold_contabil = float(val)
|
||||
|
||||
if sold_contabil is None:
|
||||
recon_rows = [
|
||||
{'INDICATOR': 'Total valoare stoc 371 (raport)', 'VALOARE_RON': f'{total_raport:,.2f}'},
|
||||
{'INDICATOR': 'Sold contabil 371 (vbal)', 'VALOARE_RON': 'INDISPONIBIL (verifica balanta vbal pe luna ref)'},
|
||||
{'INDICATOR': 'Diferenta', 'VALOARE_RON': 'N/A'}
|
||||
]
|
||||
else:
|
||||
diferenta = total_raport - sold_contabil
|
||||
pct = (diferenta / sold_contabil * 100) if sold_contabil else 0.0
|
||||
marker = ' ⚠' if abs(pct) > 1.0 else ''
|
||||
recon_rows = [
|
||||
{'INDICATOR': 'Total valoare stoc 371 (raport)', 'VALOARE_RON': f'{total_raport:,.2f}'},
|
||||
{'INDICATOR': 'Sold contabil 371 (vbal, la data ref)', 'VALOARE_RON': f'{sold_contabil:,.2f}'},
|
||||
{'INDICATOR': f'Diferenta{marker}', 'VALOARE_RON': f'{diferenta:,.2f} ({pct:+.2f}%)'}
|
||||
]
|
||||
|
||||
df_recon = pd.DataFrame(recon_rows)
|
||||
pdf_gen.add_table_section(
|
||||
"RECONCILIERE CONTABILA",
|
||||
df_recon,
|
||||
columns=['INDICATOR', 'VALOARE_RON'],
|
||||
max_rows=5
|
||||
)
|
||||
|
||||
if sold_contabil is not None and abs((total_raport - sold_contabil) / sold_contabil * 100) > 1.0:
|
||||
pdf_gen.add_explanation(
|
||||
"Diferenta >1% intre raport si soldul contabil. Cauze uzuale: evaluare "
|
||||
"pret mediu vs FIFO, inregistrari contabile ulterioare snapshot-ului vstoc, "
|
||||
"sau cont 371 analitic (371.1, 371.2) netotalizat in vbal."
|
||||
)
|
||||
perf.stop()
|
||||
|
||||
perf.start("PDF: Save document")
|
||||
pdf_gen.save()
|
||||
perf.stop()
|
||||
@@ -1131,7 +1312,14 @@ Exemple:
|
||||
default=REPORT_END_MONTH,
|
||||
help='Luna finala de raportare YYYY-MM (default: luna completa anterioara). Ex: 2026-01'
|
||||
)
|
||||
|
||||
|
||||
parser.add_argument(
|
||||
'--aging-dates',
|
||||
type=str,
|
||||
default=None,
|
||||
help='Lista YYYY-MM separata prin virgula pentru sectiunea evolutie stoc 371 >3 ani. Ex: 2022-12,2023-12,2024-12,2025-12'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--output-dir', '-o',
|
||||
type=Path,
|
||||
|
||||
108
queries.py
108
queries.py
@@ -440,6 +440,96 @@ ORDER BY zile_fara_miscare DESC NULLS FIRST
|
||||
FETCH FIRST 100 ROWS ONLY
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# 12b. STOCURI MARFĂ 371 — SUMAR PE VECHIMI (pentru dosar credit bancă)
|
||||
# =============================================================================
|
||||
STOCURI_371_SUMAR = """
|
||||
-- NOTĂ: :data_referinta = prima zi a lunii URMĂTOARE end-month (convenție proiect).
|
||||
-- (:data_referinta - 1) = ultima zi a lunii de raport — folosit pentru:
|
||||
-- (a) filtru snapshot vstoc: an=YEAR(data_ref-1), luna=MONTH(data_ref-1)
|
||||
-- (b) calcul vechime: (data_ref - 1) - NVL(dataout, datain)
|
||||
WITH s AS (
|
||||
SELECT
|
||||
s.id_grupa, s.grupa, s.id_subgrupa, s.subgrupa,
|
||||
(s.cants + s.cant - s.cante) * s.pret AS val,
|
||||
ROUND((:data_referinta - 1) - NVL(s.dataout, s.datain)) AS zile
|
||||
FROM vstoc s
|
||||
WHERE s.an = EXTRACT(YEAR FROM (:data_referinta - 1))
|
||||
AND s.luna = EXTRACT(MONTH FROM (:data_referinta - 1))
|
||||
AND s.cont = '371'
|
||||
AND (s.cants + s.cant - s.cante) <> 0
|
||||
)
|
||||
SELECT
|
||||
NVL(grupa, 'NECLASIFICAT') AS grupa,
|
||||
NVL(subgrupa, '-') AS subgrupa,
|
||||
GROUPING_ID(grupa, subgrupa) AS grouping_level,
|
||||
ROUND(SUM(val), 2) AS valoare_total,
|
||||
ROUND(SUM(CASE WHEN zile <= 180 THEN val ELSE 0 END), 2) AS val_0_6_luni,
|
||||
ROUND(SUM(CASE WHEN zile > 180 AND zile <= 365 THEN val ELSE 0 END), 2) AS val_6_12_luni,
|
||||
ROUND(SUM(CASE WHEN zile > 365 AND zile <= 730 THEN val ELSE 0 END), 2) AS val_1_2_ani,
|
||||
ROUND(SUM(CASE WHEN zile > 730 AND zile <= 1095 THEN val ELSE 0 END), 2) AS val_2_3_ani,
|
||||
ROUND(SUM(CASE WHEN zile > 1095 THEN val ELSE 0 END), 2) AS val_peste_3_ani
|
||||
FROM s
|
||||
GROUP BY ROLLUP(grupa, subgrupa)
|
||||
ORDER BY GROUPING_ID(grupa, subgrupa), grupa, subgrupa
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# 12c. STOCURI MARFĂ 371 — DETALIU ARTICOLE
|
||||
# =============================================================================
|
||||
STOCURI_371_DETALIU = """
|
||||
-- Vezi NOTA de la STOCURI_371_SUMAR.
|
||||
SELECT
|
||||
s.codmat AS cod_articol,
|
||||
s.denumire,
|
||||
s.nume_gestiune,
|
||||
NVL(s.grupa, 'NECLASIFICAT') AS grupa,
|
||||
NVL(s.subgrupa, '-') AS subgrupa,
|
||||
s.um,
|
||||
(s.cants + s.cant - s.cante) AS cantitate,
|
||||
ROUND(s.pret, 4) AS pret_unitar,
|
||||
ROUND((s.cants + s.cant - s.cante) * s.pret, 2) AS valoare,
|
||||
s.datain AS data_intrare,
|
||||
s.dataout AS data_ultima_iesire,
|
||||
ROUND((:data_referinta - 1) - NVL(s.dataout, s.datain)) AS zile_vechime,
|
||||
ROUND(((:data_referinta - 1) - NVL(s.dataout, s.datain)) / 365, 2) AS ani_vechime,
|
||||
CASE
|
||||
WHEN ROUND((:data_referinta - 1) - NVL(s.dataout, s.datain)) <= 180 THEN '0-6 luni'
|
||||
WHEN ROUND((:data_referinta - 1) - NVL(s.dataout, s.datain)) <= 365 THEN '6-12 luni'
|
||||
WHEN ROUND((:data_referinta - 1) - NVL(s.dataout, s.datain)) <= 730 THEN '1-2 ani'
|
||||
WHEN ROUND((:data_referinta - 1) - NVL(s.dataout, s.datain)) <= 1095 THEN '2-3 ani'
|
||||
ELSE '>3 ani'
|
||||
END AS bucket_vechime,
|
||||
s.lot,
|
||||
s.adata_expirare AS data_expirare,
|
||||
s.furnizor
|
||||
FROM vstoc s
|
||||
WHERE s.an = EXTRACT(YEAR FROM (:data_referinta - 1))
|
||||
AND s.luna = EXTRACT(MONTH FROM (:data_referinta - 1))
|
||||
AND s.cont = '371'
|
||||
AND (s.cants + s.cant - s.cante) <> 0
|
||||
ORDER BY zile_vechime DESC, valoare DESC
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# 12d. SOLD CONTABIL 371 (reconciliere cu raport)
|
||||
# =============================================================================
|
||||
STOCURI_371_SOLD_CONTABIL = """
|
||||
-- Sold contabil 371 la finalul lunii de raportare, din balanta de verificare (vbal).
|
||||
-- Filtrul (an, luna) = ultima luna raportata, identic cu vstoc:
|
||||
-- an = EXTRACT(YEAR FROM (:data_referinta - 1))
|
||||
-- luna= EXTRACT(MONTH FROM (:data_referinta - 1))
|
||||
-- 371 este cont de activ => sold debitor. Folosim (solddeb - soldcred) ca sa fie
|
||||
-- robust daca exista inregistrari cu sold credit (corectii). Se aduna pe toate
|
||||
-- sucursalele (id_sucursala), pentru ca raportul vstoc acopera toate gestiunile.
|
||||
SELECT
|
||||
ROUND(SUM(NVL(b.solddeb, 0) - NVL(b.soldcred, 0)), 2) AS sold_371
|
||||
FROM vbal b
|
||||
WHERE b.cont = '371'
|
||||
AND b.an = EXTRACT(YEAR FROM (:data_referinta - 1))
|
||||
AND b.luna = EXTRACT(MONTH FROM (:data_referinta - 1))
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# 13. ROTAȚIE STOCURI
|
||||
# =============================================================================
|
||||
@@ -2496,6 +2586,24 @@ QUERIES = {
|
||||
'title': '⚠️ Stoc Lent (>90 zile)',
|
||||
'description': 'Produse fără mișcare de peste 90 de zile'
|
||||
},
|
||||
'stocuri_371_sumar': {
|
||||
'sql': STOCURI_371_SUMAR,
|
||||
'params': {},
|
||||
'title': 'Stocuri Marfă 371 — Sumar Vechimi (pentru bancă)',
|
||||
'description': 'Valoarea stocului cont 371 pe grupe și buckets de vechime la data de referință'
|
||||
},
|
||||
'stocuri_371_detaliu': {
|
||||
'sql': STOCURI_371_DETALIU,
|
||||
'params': {},
|
||||
'title': 'Stocuri Marfă 371 — Detaliu Articole',
|
||||
'description': 'Lista completă articole cont 371 cu vechime și valoare'
|
||||
},
|
||||
'stocuri_371_sold_contabil': {
|
||||
'sql': STOCURI_371_SOLD_CONTABIL,
|
||||
'params': {},
|
||||
'title': 'Stocuri Marfă 371 — Sold Contabil (reconciliere)',
|
||||
'description': 'Sold 371 din rulaje la data ref pentru cross-check'
|
||||
},
|
||||
'rotatie_stocuri': {
|
||||
'sql': ROTATIE_STOCURI,
|
||||
'params': {},
|
||||
|
||||
@@ -297,7 +297,26 @@ class RecommendationsEngine:
|
||||
vezi_detalii='Sheet: Rotatie Stocuri'
|
||||
)
|
||||
|
||||
# 3. Check cash conversion cycle
|
||||
# 3. Check aged stock 371 (bank collateral haircut risk)
|
||||
stocuri_371 = results.get('stocuri_371_detaliu')
|
||||
if stocuri_371 is not None and not stocuri_371.empty and 'VALOARE' in stocuri_371.columns:
|
||||
valoare_total = stocuri_371['VALOARE'].sum()
|
||||
if valoare_total > 0 and 'BUCKET_VECHIME' in stocuri_371.columns:
|
||||
peste_3 = stocuri_371[stocuri_371['BUCKET_VECHIME'] == '>3 ani']['VALOARE'].sum()
|
||||
procent = peste_3 / valoare_total
|
||||
prag = self.thresholds.get('aged_stock_371_pct', 0.15)
|
||||
if procent > prag:
|
||||
self._add_recommendation(
|
||||
categorie='Stoc 371',
|
||||
indicator='Marfa invechita cont 371',
|
||||
valoare=f'{procent*100:.1f}%',
|
||||
status='CONCENTRARE',
|
||||
explicatie=f'{procent*100:.1f}% din valoarea stocului 371 are >3 ani ({peste_3:,.0f} RON). Banca aplica haircut la evaluare colateral.',
|
||||
recomandare='Lichidare / provizionare recomandata inainte de depunere dosar credit.',
|
||||
vezi_detalii='Sheet: stocuri_371_detaliu'
|
||||
)
|
||||
|
||||
# 4. Check cash conversion cycle
|
||||
ciclu = results.get('ciclu_conversie_cash')
|
||||
if ciclu is not None and not ciclu.empty:
|
||||
dio_row = ciclu[ciclu['INDICATOR'].str.contains('stoc|DIO', case=False, na=False)]
|
||||
|
||||
19
run.bat
19
run.bat
@@ -1,5 +1,22 @@
|
||||
@echo off
|
||||
REM Run Data Intelligence Report Generator (Windows)
|
||||
REM =============================================================================
|
||||
REM Data Intelligence Report Generator - ROMFAST
|
||||
REM =============================================================================
|
||||
REM
|
||||
REM Argumente disponibile:
|
||||
REM --months N Numar luni pentru analiza (default: 12)
|
||||
REM --end-month YYYY-MM Luna finala de raportare (default: luna precedenta)
|
||||
REM Ex: --end-month 2025-12
|
||||
REM --aging-dates LIST Lista YYYY-MM pentru evolutie stoc 371 >3 ani
|
||||
REM Ex: --aging-dates 2022-12,2023-12,2024-12,2025-12
|
||||
REM --output-dir PATH Director output (default: ./output)
|
||||
REM
|
||||
REM Exemple:
|
||||
REM run.bat (raport standard)
|
||||
REM run.bat --end-month 2025-12 (raport dosar banca)
|
||||
REM run.bat --end-month 2025-12 --aging-dates 2022-12,2023-12,2024-12,2025-12
|
||||
REM (raport dosar banca + trend)
|
||||
REM =============================================================================
|
||||
|
||||
cd /d "%~dp0"
|
||||
call .venv\Scripts\activate.bat
|
||||
|
||||
Reference in New Issue
Block a user