Commit 3dc33af2 authored by DevPilot's avatar DevPilot

fix(accounting): حساب ض.ق.م المدخلات المكرر بنوع غلط + توضيح قفل كود الحساب النظامي

١) «ضريبة القيمة المضافة على المدخلات» اتعمل بالاستيراد كـ liability لأن
   ملف الشجرة حاططه تحت الالتزامات — وده غلط: ضريبة المدخلات بتسترد من
   المصلحة يعني أصل مش التزام. وكمان كان مكرر: الحساب الصح موجود أصلاً
   (12041106 ضريبة القيمة المضافة — مدخلات، asset، نظامي، وعليه قيد فعلي).
   الحساب المكرر (2308040418) اتشال — مكانش عليه أي قيد ولا مربوط بأي
   قاعدة ترحيل ولا حساب بنكي ولا خزنة.

٢) حارس دائم في أداة الاستيراد: معنى الاسم بيكسب على موقع البند في الملف.
   لو الملف حطّ «مدخلات» تحت الالتزامات، الأداة بتصحّحه لأصل. وكمان
   اتضافت حروف الجر للكلمات المهملة في المطابقة، لأن «ض.ق.م على المدخلات»
   مكانش بيتطابق مع «ض.ق.م — مدخلات» بسبب كلمة «على» بس. النتيجة إن
   إعادة تشغيل الاستيراد دلوقتي بتقول ٢٩٨ موجود و٠ جديد — يعني الأداة بقت
   idempotent وما بتعملش نسخ مكررة.

٣) رسالة «لا يمكن تعديل كود حساب نظامي» كانت بتوقف من غير ما تقول ليه ولا
   إيه المسموح. بقت توضّح إن الكود هو اللي محرّك الترحيل بيلاقي الحساب بيه
   فتغييره بيوقّف الترحيل، وإن الاسم والنوع والطبيعة قابلين للتعديل عادي.

٤) مسح شامل على كل الدليل: كل الحسابات الضريبية نوعها سليم دلوقتي —
   المدخلات والضريبة المؤجلة أصول، وكل ضرائب المخرجات والدمغة التزامات.
   والقيد الوحيد على حساب ضريبي (JE-2026-001093 شراء بن) مدين على ضريبة
   المدخلات وده الاتجاه الصح.

٥) اتأرشف حساب 1908 «حساب» — اسم بلا معنى، نوعه مصروف تحت أب أصول، من غير
   أي قيود ولا استخدام. دلوقتي مفيش ولا حساب نوعه مختلف عن أبوه.

الميزان 513,122,428.03 مدين = دائن، و٩٨٧ حساب نشط.
parent e623ab6f
...@@ -131,10 +131,15 @@ class ChartOfAccountsController extends Controller ...@@ -131,10 +131,15 @@ class ChartOfAccountsController extends Controller
'account_nature' => 'required|in:debit,credit', 'account_nature' => 'required|in:debit,credit',
]); ]);
// Cannot change code of system accounts // كود الحساب النظامي ثابت: محرّك الترحيل بيلاقي الحساب بالكود ده، فتغييره
// بيكسر الربط ويوقّف الترحيل التلقائي. باقي بيانات الحساب (الاسم، النوع،
// الطبيعة، التفعيل) قابلة للتعديل عادي.
if ((int) $account->is_system === 1 && ($request->post('account_code', '') !== $account->account_code)) { if ((int) $account->is_system === 1 && ($request->post('account_code', '') !== $account->account_code)) {
$session = App::getInstance()->session(); $session = App::getInstance()->session();
$session->flash('_alerts', [['type' => 'error', 'message' => 'لا يمكن تعديل كود حساب نظامي']]); $session->flash('_alerts', [['type' => 'error', 'message' =>
'كود الحساب النظامي (' . $account->account_code . ') ثابت — محرّك الترحيل بيلاقي '
. 'الحساب بالكود ده وتغييره بيوقّف الترحيل التلقائي. تقدر تعدّل الاسم والنوع '
. 'والطبيعة عادي من غير ما تغيّر الكود.']]);
return $this->redirect('/accounting/chart-of-accounts/' . (int) $id . '/edit'); return $this->redirect('/accounting/chart-of-accounts/' . (int) $id . '/edit');
} }
......
...@@ -55,7 +55,10 @@ PARENT_OVERRIDE = { ...@@ -55,7 +55,10 @@ PARENT_OVERRIDE = {
} }
STOP_RAW = ['ايراد', 'ايرادات', 'حساب', 'حسابات', 'مصاريف', 'مصروفات', STOP_RAW = ['ايراد', 'ايرادات', 'حساب', 'حسابات', 'مصاريف', 'مصروفات',
'اكاديمية', 'اكاديميه', 'مصروف', 'وسيط', 'قيمة', 'قيمه'] 'اكاديمية', 'اكاديميه', 'مصروف', 'وسيط', 'قيمة', 'قيمه',
# حروف جر وروابط — وجودها بيمنع مطابقة أسماء متطابقة فعلياً
# زي «ض.ق.م على المدخلات» مقابل «ض.ق.م — مدخلات»
'على', 'علي', 'عن', 'من', 'الى', 'إلى', 'في', 'فى', 'مع']
def norm(s): def norm(s):
...@@ -77,6 +80,24 @@ def toks(s): ...@@ -77,6 +80,24 @@ def toks(s):
return {stem(w) for w in norm(s).split() if stem(w) not in STOP and len(stem(w)) > 2} return {stem(w) for w in norm(s).split() if stem(w) not in STOP and len(stem(w)) > 2}
# الملف نفسه ممكن يحط بند تحت جذر غلط. ضريبة «المدخلات» مثلاً أصل (بتسترد
# من المصلحة) مش التزام، حتى لو الملف حاططها تحت الالتزامات. القواعد دي
# بتصحّح النوع حسب معنى الاسم، وبتمنع إن غلطة في الملف تدخل الدليل.
SEMANTIC_TYPE = [
(('مدخلات',), 'asset'),
(('ضريبة مؤجلة', 'أصول ضريبة'), 'asset'),
(('سلف من الغير', 'سلف الغير'), 'liability'),
]
def semantic_type(name, default_type):
n = norm(name)
for words, atype in SEMANTIC_TYPE:
if any(norm(w) in n for w in words):
return atype
return default_type
def env(): def env():
e = {} e = {}
for line in open(os.path.join(ROOT, '.env'), encoding='utf-8'): for line in open(os.path.join(ROOT, '.env'), encoding='utf-8'):
...@@ -191,6 +212,10 @@ def main(): ...@@ -191,6 +212,10 @@ def main():
name = path[depth] name = path[depth]
is_leaf = depth == len(path) - 1 is_leaf = depth == len(path) - 1
# لو معنى الاسم بيقول نوع تاني، الاسم بيكسب — الملف ممكن يغلط
atype = semantic_type(name, ROOT_MAP[root][1])
nature = 'debit' if atype in ('asset', 'expense') else 'credit'
hit = find(name, atype, headers_only=(False if is_leaf else True)) hit = find(name, atype, headers_only=(False if is_leaf else True))
if hit is None and not is_leaf: if hit is None and not is_leaf:
hit = find(name, atype, headers_only=None) hit = find(name, atype, headers_only=None)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment