stat2025 commited on
Commit
0bd5347
·
verified ·
1 Parent(s): 7df0226

Upload 2 files

Browse files
Files changed (2) hide show
  1. apps-script/Code.gs +153 -0
  2. apps-script/README.md +14 -0
apps-script/Code.gs ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const CONFIG = Object.freeze({
2
+ folderId: "1QBsYtQtUp5-uhqjDRugvxHZinozIu6YY",
3
+ spreadsheetId: "1_LiYMMphsJaRRJje0pxKrNSiC1ahBS3GnHM01vPrrCk",
4
+ sheetName: "الورقة1",
5
+ maxPhotos: 3,
6
+ maxPhotoBytes: 3 * 1024 * 1024,
7
+ });
8
+
9
+ const HEADERS = [
10
+ "رقم التوثيق",
11
+ "تاريخ ووقت التسجيل",
12
+ "اسم الباحث/ة",
13
+ "اسم المنشأة",
14
+ "السجل التجاري",
15
+ "رقم العقد",
16
+ "المدينة الصناعية",
17
+ "حالة التوثيق",
18
+ "الإفادة الميدانية",
19
+ "روابط الصور",
20
+ "رابط مجلد المنشأة",
21
+ "عدد الصور",
22
+ ];
23
+
24
+ function doGet() {
25
+ return jsonResponse({ ok: true, service: "ICS2 Documentation", version: 1 });
26
+ }
27
+
28
+ function authorizeService() {
29
+ DriveApp.getFolderById(CONFIG.folderId).getName();
30
+ SpreadsheetApp.openById(CONFIG.spreadsheetId).getName();
31
+ return "AUTHORIZED";
32
+ }
33
+
34
+ function doPost(event) {
35
+ const lock = LockService.getScriptLock();
36
+ try {
37
+ lock.waitLock(20000);
38
+ const request = JSON.parse(event.postData.contents || "{}");
39
+ validateAccess(request.accessCode);
40
+ validateRequest(request);
41
+
42
+ const sheet = getSheet();
43
+ ensureHeaders(sheet);
44
+ const existing = sheet
45
+ .getRange(1, 1, Math.max(sheet.getLastRow(), 1), 1)
46
+ .getDisplayValues()
47
+ .flat()
48
+ .includes(request.documentationId);
49
+ if (existing) return jsonResponse({ ok: true, duplicate: true, documentationId: request.documentationId });
50
+
51
+ const folder = getEvidenceFolder(request);
52
+ const photoUrls = request.photos.map((photo, index) => savePhoto(folder, photo, request, index));
53
+ sheet.appendRow([
54
+ request.documentationId,
55
+ new Date(),
56
+ safeText(request.researcher),
57
+ safeText(request.establishmentName),
58
+ safeText(request.commercialRecord),
59
+ safeText(request.contractNumber) || "لا يوجد رقم عقد",
60
+ safeText(request.city),
61
+ safeText(request.fieldStatus),
62
+ safeText(request.statement),
63
+ photoUrls.join("\n"),
64
+ folder.getUrl(),
65
+ photoUrls.length,
66
+ ]);
67
+
68
+ return jsonResponse({
69
+ ok: true,
70
+ documentationId: request.documentationId,
71
+ folderUrl: folder.getUrl(),
72
+ photoUrls,
73
+ });
74
+ } catch (error) {
75
+ return jsonResponse({ ok: false, error: String(error.message || error) });
76
+ } finally {
77
+ try {
78
+ lock.releaseLock();
79
+ } catch (_) {}
80
+ }
81
+ }
82
+
83
+ function validateAccess(accessCode) {
84
+ const expected = PropertiesService.getScriptProperties().getProperty("ACCESS_CODE") || "20302030";
85
+ if (String(accessCode || "") !== expected) throw new Error("غير مصرح بحفظ التوثيق.");
86
+ }
87
+
88
+ function validateRequest(request) {
89
+ if (!request.documentationId) throw new Error("رقم التوثيق مفقود.");
90
+ if (!safeText(request.researcher)) throw new Error("اسم الباحث مفقود.");
91
+ if (!safeText(request.establishmentName)) throw new Error("اسم المنشأة مفقود.");
92
+ if (!safeText(request.fieldStatus)) throw new Error("حالة التوثيق مطلوبة.");
93
+ if (!safeText(request.statement)) throw new Error("الإفادة الميدانية مطلوبة.");
94
+ if (!Array.isArray(request.photos) || request.photos.length < 1) throw new Error("يجب إرفاق صورة واحدة على الأقل.");
95
+ if (request.photos.length > CONFIG.maxPhotos) throw new Error("الحد الأعلى 3 صور.");
96
+ }
97
+
98
+ function getSheet() {
99
+ const spreadsheet = SpreadsheetApp.openById(CONFIG.spreadsheetId);
100
+ return spreadsheet.getSheetByName(CONFIG.sheetName) || spreadsheet.insertSheet(CONFIG.sheetName);
101
+ }
102
+
103
+ function ensureHeaders(sheet) {
104
+ const current = sheet.getRange(1, 1, 1, HEADERS.length).getDisplayValues()[0];
105
+ if (current.join("") === "") {
106
+ sheet.getRange(1, 1, 1, HEADERS.length).setValues([HEADERS]);
107
+ sheet.setFrozenRows(1);
108
+ sheet.getRange(1, 1, 1, HEADERS.length)
109
+ .setFontWeight("bold")
110
+ .setBackground("#4137A8")
111
+ .setFontColor("#FFFFFF");
112
+ sheet.autoResizeColumns(1, HEADERS.length);
113
+ }
114
+ }
115
+
116
+ function getEvidenceFolder(request) {
117
+ const root = DriveApp.getFolderById(CONFIG.folderId);
118
+ const researcherFolder = getOrCreateFolder(root, safeFileName(request.researcher));
119
+ const cityFolder = getOrCreateFolder(researcherFolder, safeFileName(request.city || "مدينة غير محددة"));
120
+ const entityName = `${safeText(request.commercialRecord) || "دون سجل"} - ${safeText(request.establishmentName)}`;
121
+ return getOrCreateFolder(cityFolder, safeFileName(entityName));
122
+ }
123
+
124
+ function getOrCreateFolder(parent, name) {
125
+ const folders = parent.getFoldersByName(name);
126
+ return folders.hasNext() ? folders.next() : parent.createFolder(name);
127
+ }
128
+
129
+ function savePhoto(folder, photo, request, index) {
130
+ const mimeType = String(photo.mimeType || "");
131
+ if (!/^image\/(jpeg|png|webp)$/.test(mimeType)) throw new Error("نوع الصورة غير مدعوم.");
132
+ const bytes = Utilities.base64Decode(String(photo.base64 || ""));
133
+ if (bytes.length > CONFIG.maxPhotoBytes) throw new Error("إحدى الصور أكبر من الحد المسموح.");
134
+ const extension = mimeType === "image/png" ? "png" : mimeType === "image/webp" ? "webp" : "jpg";
135
+ const timestamp = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd-HHmmss");
136
+ const name = safeFileName(
137
+ `${request.commercialRecord || "دون-سجل"}-${timestamp}-${index + 1}.${extension}`,
138
+ );
139
+ return folder.createFile(Utilities.newBlob(bytes, mimeType, name)).getUrl();
140
+ }
141
+
142
+ function safeText(value) {
143
+ return String(value == null ? "" : value).trim().slice(0, 2000);
144
+ }
145
+
146
+ function safeFileName(value) {
147
+ return safeText(value).replace(/[\\/:*?"<>|#%{}[\]]/g, "-").slice(0, 140) || "غير محدد";
148
+ }
149
+
150
+ function jsonResponse(payload) {
151
+ return ContentService.createTextOutput(JSON.stringify(payload))
152
+ .setMimeType(ContentService.MimeType.JSON);
153
+ }
apps-script/README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # تفعيل خدمة التوثيق
2
+
3
+ 1. افتح `script.google.com` وأنشئ مشروعًا جديدًا باسم `ICS2 Documentation`.
4
+ 2. انسخ محتوى `Code.gs` إلى المشروع.
5
+ 3. يمكن تغيير رمز الخدمة لاحقًا من **إعدادات المشروع > خصائص البرنامج النصي** بإضافة `ACCESS_CODE`.
6
+ 4. شغّل الدالة `authorizeService` مرة واحدة، ووافق على صلاحيات Drive وSheets.
7
+ 5. اختر **نشر > نشر جديد > تطبيق ويب**:
8
+ - التنفيذ باسم: حسابك
9
+ - من لديه حق الوصول: أي شخص
10
+ 6. انسخ رابط `/exec` الناتج.
11
+ 7. ضعه في `documentation-config.js` داخل قيمة `endpoint`.
12
+ 8. ارفع ملفات النظام المحدثة إلى Hugging Face.
13
+
14
+ المجلد والجدول مربوطان مسبقًا بالمعرفات التي زود بها المستخدم.