أتمتة فواتير Gmail إلى Sheets بدون نسخ ولصق
مستوى القارئ: متوسط
هتخرج من المقال ده بworkflow بسيط يوفر عليك حوالي 45 دقيقة أسبوعيًا: يقرأ فواتير Gmail، يحفظ مرفقات PDF في Drive، ويسجل صف واضح في Google Sheets.
المشكلة باختصار
لو عندك شركة صغيرة أو فريق مشتريات بيستقبل 20 إلى 40 فاتورة في الأسبوع، النسخ واللصق اليدوي بيعمل مشكلتين. الأولى إن فاتورة ممكن تضيع وسط البريد. الثانية إن نفس الفاتورة ممكن تتسجل مرتين لو أكثر من شخص فتحها.
الطريقة الشائعة هي تحميل كل PDF يدويًا ثم تسمية الملف وكتابة المورد والتاريخ في Sheet. الطريقة دي بتفشل لما البريد يزيد، أو لما المورد يرسل نفس الفاتورة مرة تانية بعد تعديل بسيط.
الفكرة الأساسية
ركز. إحنا مش بنبني نظام محاسبة. إحنا بنبني طبقة تنظيم أولى. Apps Script يشتغل كل ساعة، يبحث في Gmail عن رسائل عليها label اسمه Invoices، يأخذ أول مرفق PDF، يحفظه في Drive، ثم يضيف صف في Sheet.
الافتراض إن عندك حجم صغير أو متوسط: أقل من 50 رسالة فاتورة في التشغيل الواحد. لو عندك آلاف الفواتير يوميًا، انت محتاج queue ونظام معالجة مخصص، مش سكربت داخل Google Workspace.
المفهوم المهم هنا اسمه dedupe. يعني منع التكرار. مثال بسيط: لو عامل أمن بيسجل أسماء الزوار في دفتر، لازم يبص هل الاسم اتكتب قبل كده في نفس اليوم. في السكربت، هنستخدم messageId كرقم زيارة. لو موجود في Sheet، الرسالة تتساب.
الإعداد العملي
- اعمل label في Gmail اسمه
Invoicesوحط عليه رسائل الفواتير يدويًا أو بفلتر Gmail. - اعمل Google Sheet باسم
Invoice Registerواكتب الأعمدة: Date, Vendor, Subject, File URL, Message ID. - اعمل folder في Google Drive باسم
Invoices Archive. - افتح Apps Script من داخل Google Sheets، ثم الصق الكود التالي.
const DRIVE_FOLDER_ID = 'PUT_FOLDER_ID_HERE';
const SHEET_NAME = 'Invoices';
function importInvoicesFromGmail() {
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
const folder = DriveApp.getFolderById(DRIVE_FOLDER_ID);
const processedIds = new Set(
sheet.getRange(2, 5, Math.max(sheet.getLastRow() - 1, 1), 1)
.getValues().flat().filter(Boolean)
);
const threads = GmailApp.search('label:Invoices has:attachment filename:pdf newer_than:30d', 0, 50);
threads.forEach(thread => {
thread.getMessages().forEach(message => {
const messageId = message.getId();
if (processedIds.has(messageId)) return;
const pdf = message.getAttachments().find(a =>
a.getContentType() === 'application/pdf'
);
if (!pdf) return;
const vendor = message.getFrom().replace(/<.*>/, '').trim();
const file = folder.createFile(pdf.copyBlob())
.setName(`${message.getDate().toISOString().slice(0,10)}-${pdf.getName()}`);
sheet.appendRow([
message.getDate(), vendor, message.getSubject(), file.getUrl(), messageId
]);
processedIds.add(messageId);
});
});
}