33< head >
44 < meta charset ="UTF-8 ">
55 < meta name ="viewport " content ="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no ">
6- < title > فَوْتَرَة كامل - نظام الفوترة الجزائري (كاميرا خلفية للمسح )</ title >
6+ < title > فَوْتَرَة كامل - نظام الفوترة الجزائري (إضافة منتج من QR )</ title >
77 < script src ="https://cdn.tailwindcss.com "> </ script >
88 < link rel ="preconnect " href ="https://fonts.googleapis.com ">
99 < link rel ="preconnect " href ="https://fonts.gstatic.com " crossorigin >
106106 .product-grid-card .add-btn : hover { transform : scale (1.1 ); background : # 0284c7 ; }
107107 .dark .product-grid-card .add-btn { background : # 0284c7 ; }
108108
109- /* تنسيق زر تبديل الكاميرا في الماسح */
110109 .scanner-controls {
111110 display : flex;
112111 justify-content : center;
154153 </ div >
155154 < div >
156155 < h1 class ="font-extrabold text-lg leading-none "> فَوْتَرَة</ h1 >
157- < p class ="text-[11px] text-slate-500 dark:text-slate-400 "> الإصدار الجزائري v3.9 </ p >
156+ < p class ="text-[11px] text-slate-500 dark:text-slate-400 "> الإصدار الجزائري v4.0 </ p >
158157 </ div >
159158 </ div >
160159 < nav class ="p-3 space-y-1.5 overflow-y-auto ">
@@ -502,7 +501,6 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
502501 < button id ="close-scan " class ="w-9 h-9 grid place-items-center rounded-xl bg-white/10 " aria-label ="إغلاق الماسح "> < i class ="fa-solid fa-xmark "> </ i > </ button >
503502 </ div >
504503 < div id ="reader " class ="w-full aspect-square bg-black "> </ div >
505- <!-- أزرار التحكم في الماسح -->
506504 < div class ="scanner-controls ">
507505 < span class ="camera-label " id ="camera-label "> 📷 كاميرا: خلفية</ span >
508506 < button id ="toggle-camera " class ="flex items-center gap-1 ">
@@ -716,8 +714,10 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
716714 }
717715
718716 // ============================================================
719- // PRODUCTS (with images)
717+ // PRODUCTS (with images and scan-to-add )
720718 // ============================================================
719+ let pendingAddToInvoice = false ; // لتحديد ما إذا نضيف المنتج للفاتورة بعد الحفظ
720+
721721 function renderProducts ( ) {
722722 $ ( '#products-table' ) . innerHTML = state . products . map ( p => `
723723 <tr class="border-b dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/50">
@@ -758,12 +758,14 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
758758
759759 $ ( '#btn-add-product' ) . onclick = ( ) => openProduct ( ) ;
760760
761- function openProduct ( p = null ) {
762- $ ( '#modal-title' ) . textContent = p ? 'تعديل منتج' : 'منتج جديد' ;
761+ function openProduct ( p = null , ref = '' , addToInvoice = false ) {
762+ pendingAddToInvoice = addToInvoice ;
763+ $ ( '#modal-title' ) . textContent = p ? 'تعديل منتج' : ( ref ? 'إضافة منتج جديد من المسح' : 'منتج جديد' ) ;
764+ const refValue = p ?. ref || ref || '' ;
763765 $ ( '#modal-body' ) . innerHTML = `
764766 <div class="grid sm:grid-cols-2 gap-3">
765767 <div class="sm:col-span-2"><label class="text-xs">اسم المنتج</label><input id="p-name" value="${ p ?. name || '' } " class="w-full h-11 px-3 rounded-xl bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700"></div>
766- <div><label class="text-xs">المرجع / باركود</label><input id="p-ref" value="${ p ?. ref || '' } " class="w-full h-11 px-3 rounded-xl bg-slate-50 dark:bg-slate-800 border"></div>
768+ <div><label class="text-xs">المرجع / باركود</label><input id="p-ref" value="${ refValue } " class="w-full h-11 px-3 rounded-xl bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 "></div>
767769 <div><label class="text-xs">المخزون</label><input id="p-stock" type="number" value="${ p ?. stock || 0 } " class="w-full h-11 px-3 rounded-xl bg-slate-50 dark:bg-slate-800 border"></div>
768770 <div><label class="text-xs">سعر الشراء</label><input id="p-buy" type="number" value="${ p ?. buy || 0 } " class="w-full h-11 px-3 rounded-xl bg-slate-50 dark:bg-slate-800 border"></div>
769771 <div><label class="text-xs">سعر البيع HT</label><input id="p-price" type="number" value="${ p ?. price || 0 } " class="w-full h-11 px-3 rounded-xl bg-slate-50 dark:bg-slate-800 border"></div>
@@ -826,17 +828,28 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
826828 const tva = + $ ( '#p-tva' ) . value || state . company . tva ;
827829 const stock = + $ ( '#p-stock' ) . value || 0 ;
828830 const o = { id : id || Date . now ( ) , name, ref, buy, price, tva, stock, image : imageData } ;
831+ let isNew = false ;
829832 updateState ( s => {
830833 if ( id ) {
831834 const idx = s . products . findIndex ( p => p . id === id ) ;
832835 if ( idx > - 1 ) s . products [ idx ] = o ;
833836 } else {
834837 s . products . unshift ( o ) ;
838+ isNew = true ;
835839 }
836840 } ) ;
837841 showModal ( false ) ;
838842 renderProducts ( ) ;
839843 showToast ( 'تم حفظ المنتج' , 'success' ) ;
844+
845+ // إذا كنا في وضع الإضافة من المسح، نضيف المنتج للفاتورة
846+ if ( pendingAddToInvoice && isNew ) {
847+ // نضيف المنتج للفاتورة باستخدام المعرف الجديد (o.id)
848+ addItemById ( o . id ) ;
849+ pendingAddToInvoice = false ;
850+ // ننتقل إلى صفحة البيع إذا لم نكن فيها
851+ go ( 'pos' ) ;
852+ }
840853 }
841854
842855 function editProduct ( id ) { openProduct ( state . products . find ( p => p . id === id ) ) ; }
@@ -851,7 +864,7 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
851864 }
852865
853866 // ============================================================
854- // CUSTOMERS (unchanged)
867+ // CUSTOMERS
855868 // ============================================================
856869 function renderCustomers ( ) {
857870 $ ( '#customers-table' ) . innerHTML = state . customers . map ( c => `<tr class="border-b dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/50">
@@ -1349,22 +1362,18 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
13491362 document . addEventListener ( 'click' , e => { if ( e . target . matches ( '[data-close]' ) ) showModal ( false ) ; } ) ;
13501363
13511364 // ============================================================
1352- // SCANNER (محسّن لاستخدام الكاميرا الخلفية )
1365+ // SCANNER (محسّن: إضافة منتج عند عدم العثور عليه )
13531366 // ============================================================
13541367 let html5Qr = null ;
13551368 let currentCameraId = null ;
13561369 let availableCameras = [ ] ;
13571370
1358- // دالة للحصول على الكاميرا المفضلة (خلفية إن وجدت)
13591371 function getPreferredCameraId ( cameras ) {
1360- // البحث عن كاميرا خلفية (environment)
13611372 const backCamera = cameras . find ( cam => cam . label . toLowerCase ( ) . includes ( 'back' ) || cam . label . toLowerCase ( ) . includes ( 'rear' ) || cam . label . toLowerCase ( ) . includes ( 'environment' ) ) ;
13621373 if ( backCamera ) return backCamera . id ;
1363- // إذا لم توجد، نأخذ الأولى
13641374 return cameras [ 0 ] ?. id || null ;
13651375 }
13661376
1367- // دالة لبدء الماسح بكاميرا محددة
13681377 async function startScanner ( cameraId ) {
13691378 try {
13701379 if ( html5Qr ) {
@@ -1374,12 +1383,10 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
13741383 const config = { fps : 10 , qrbox : 250 } ;
13751384 await html5Qr . start ( cameraId , config , onScanSuccess ) ;
13761385 currentCameraId = cameraId ;
1377- // تحديث اسم الكاميرا المعروض
13781386 const cam = availableCameras . find ( c => c . id === cameraId ) ;
13791387 const label = cam ? cam . label : 'غير معروف' ;
13801388 const isBack = label . toLowerCase ( ) . includes ( 'back' ) || label . toLowerCase ( ) . includes ( 'rear' ) || label . toLowerCase ( ) . includes ( 'environment' ) ;
13811389 document . getElementById ( 'camera-label' ) . textContent = `📷 كاميرا: ${ isBack ? 'خلفية' : 'أمامية' } ` ;
1382- // حفظ اختيار الكاميرا
13831390 localStorage . setItem ( 'preferred_camera' , cameraId ) ;
13841391 } catch ( err ) {
13851392 console . error ( 'خطأ في بدء الماسح:' , err ) ;
@@ -1388,30 +1395,30 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
13881395 }
13891396 }
13901397
1391- // دالة معالجة نتيجة المسح
13921398 function onScanSuccess ( decodedText ) {
1393- try {
1394- if ( html5Qr ) html5Qr . stop ( ) ;
1395- } catch ( e ) { }
1399+ try { if ( html5Qr ) html5Qr . stop ( ) ; } catch ( e ) { }
13961400 $ ( '#scanner' ) . classList . add ( 'hidden' ) ;
1397- // البحث عن المنتج بالباركود
1401+
13981402 const prod = state . products . find ( p => p . ref === decodedText ) ;
13991403 if ( prod ) {
14001404 addItemById ( prod . id ) ;
14011405 go ( 'pos' ) ;
14021406 showToast ( 'تم إضافة المنتج: ' + prod . name , 'success' ) ;
14031407 } else {
1404- showToast ( 'الباركود: ' + decodedText + ' (غير مسجل)' , 'info' ) ;
1408+ // عرض خيار إضافة منتج جديد بالمرجع المقروء
1409+ if ( confirm ( `الباركود "${ decodedText } " غير مسجل. هل تريد إضافة منتج جديد بهذا المرجع؟` ) ) {
1410+ openProduct ( null , decodedText , true ) ; // addToInvoice = true
1411+ } else {
1412+ showToast ( 'الباركود: ' + decodedText + ' (غير مسجل)' , 'info' ) ;
1413+ }
14051414 }
14061415 }
14071416
1408- // أزرار فتح الماسح
14091417 $ ( '#btn-scan' ) . onclick = $ ( '#btn-scan-add' ) . onclick = async function ( ) {
14101418 try {
14111419 Sound . camera ( ) ;
14121420 $ ( '#scanner' ) . classList . remove ( 'hidden' ) ;
14131421
1414- // الحصول على قائمة الكاميرات المتاحة
14151422 const cameras = await Html5Qrcode . getCameras ( ) ;
14161423 if ( cameras . length === 0 ) {
14171424 showToast ( 'لا توجد كاميرا متاحة' , 'error' ) ;
@@ -1420,9 +1427,7 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
14201427 }
14211428 availableCameras = cameras ;
14221429
1423- // تحديد الكاميرا المفضلة: من localStorage أو الكاميرا الخلفية
14241430 let preferredId = localStorage . getItem ( 'preferred_camera' ) ;
1425- // التحقق من صحة المعرف
14261431 if ( ! preferredId || ! cameras . some ( c => c . id === preferredId ) ) {
14271432 preferredId = getPreferredCameraId ( cameras ) ;
14281433 }
@@ -1436,13 +1441,11 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
14361441 }
14371442 } ;
14381443
1439- // زر تبديل الكاميرا
14401444 $ ( '#toggle-camera' ) . onclick = async function ( ) {
14411445 if ( availableCameras . length < 2 ) {
14421446 showToast ( 'لا توجد كاميرات أخرى للتبديل' , 'warning' ) ;
14431447 return ;
14441448 }
1445- // البحث عن الكاميرا التالية في القائمة
14461449 const currentIndex = availableCameras . findIndex ( c => c . id === currentCameraId ) ;
14471450 const nextIndex = ( currentIndex + 1 ) % availableCameras . length ;
14481451 const nextCamera = availableCameras [ nextIndex ] ;
@@ -1452,11 +1455,8 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
14521455 }
14531456 } ;
14541457
1455- // إغلاق الماسح
14561458 $ ( '#close-scan' ) . onclick = async function ( ) {
1457- try {
1458- if ( html5Qr ) await html5Qr . stop ( ) ;
1459- } catch ( e ) { }
1459+ try { if ( html5Qr ) await html5Qr . stop ( ) ; } catch ( e ) { }
14601460 $ ( '#scanner' ) . classList . add ( 'hidden' ) ;
14611461 } ;
14621462
0 commit comments