stat2025 commited on
Commit
abbc3cd
·
verified ·
1 Parent(s): 4d77499

Upload Code.gs

Browse files
Files changed (1) hide show
  1. apps-script/Code.gs +93 -11
apps-script/Code.gs CHANGED
@@ -2,6 +2,7 @@ const CONFIG = Object.freeze({
2
  folderId: "1QBsYtQtUp5-uhqjDRugvxHZinozIu6YY",
3
  spreadsheetId: "1_LiYMMphsJaRRJje0pxKrNSiC1ahBS3GnHM01vPrrCk",
4
  sheetName: "الورقة1",
 
5
  maxPhotos: 3,
6
  maxPhotoBytes: 3 * 1024 * 1024,
7
  });
@@ -19,10 +20,40 @@ const HEADERS = [
19
  "روابط الصور",
20
  "رابط مجلد المنشأة",
21
  "عدد الصور",
 
22
  ];
23
 
24
- function doGet() {
25
- return jsonResponse({ ok: true, service: "ICS2 Documentation", version: 1 });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
 
28
  function authorizeService() {
@@ -36,17 +67,18 @@ function doPost(event) {
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));
@@ -63,11 +95,14 @@ function doPost(event) {
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
  });
@@ -80,21 +115,66 @@ function doPost(event) {
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);
@@ -110,7 +190,9 @@ function ensureHeaders(sheet) {
110
  .setBackground("#4137A8")
111
  .setFontColor("#FFFFFF");
112
  sheet.autoResizeColumns(1, HEADERS.length);
 
113
  }
 
114
  }
115
 
116
  function getEvidenceFolder(request) {
@@ -132,7 +214,7 @@ function savePhoto(folder, photo, request, index) {
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
  );
 
2
  folderId: "1QBsYtQtUp5-uhqjDRugvxHZinozIu6YY",
3
  spreadsheetId: "1_LiYMMphsJaRRJje0pxKrNSiC1ahBS3GnHM01vPrrCk",
4
  sheetName: "الورقة1",
5
+ timezone: "Asia/Riyadh",
6
  maxPhotos: 3,
7
  maxPhotoBytes: 3 * 1024 * 1024,
8
  });
 
20
  "روابط الصور",
21
  "رابط مجلد المنشأة",
22
  "عدد الصور",
23
+ "مفتاح العينة",
24
  ];
25
 
26
+ function doGet(event) {
27
+ try {
28
+ const params = (event && event.parameter) || {};
29
+ const action = String(params.action || "health");
30
+ if (action === "health") {
31
+ return jsonResponse({ ok: true, service: "ICS2 Documentation", version: 2 });
32
+ }
33
+
34
+ if (action === "status") {
35
+ validateResearcherAccess(params.accessCode);
36
+ return jsonResponse({
37
+ ok: true,
38
+ generatedAt: new Date().toISOString(),
39
+ records: getDocumentationRecords(false),
40
+ });
41
+ }
42
+
43
+ if (action === "admin") {
44
+ validateSupervisorAccess(params.accessCode);
45
+ return jsonResponse({
46
+ ok: true,
47
+ generatedAt: new Date().toISOString(),
48
+ timezone: CONFIG.timezone,
49
+ records: getDocumentationRecords(true),
50
+ });
51
+ }
52
+
53
+ throw new Error("الطلب غير معروف.");
54
+ } catch (error) {
55
+ return jsonResponse({ ok: false, error: String(error.message || error) });
56
+ }
57
  }
58
 
59
  function authorizeService() {
 
67
  try {
68
  lock.waitLock(20000);
69
  const request = JSON.parse(event.postData.contents || "{}");
70
+ validateResearcherAccess(request.accessCode);
71
  validateRequest(request);
72
 
73
  const sheet = getSheet();
74
  ensureHeaders(sheet);
75
+ const existingIds = sheet
76
  .getRange(1, 1, Math.max(sheet.getLastRow(), 1), 1)
77
  .getDisplayValues()
78
+ .flat();
79
+ if (existingIds.includes(request.documentationId)) {
80
+ return jsonResponse({ ok: true, duplicate: true, documentationId: request.documentationId });
81
+ }
82
 
83
  const folder = getEvidenceFolder(request);
84
  const photoUrls = request.photos.map((photo, index) => savePhoto(folder, photo, request, index));
 
95
  photoUrls.join("\n"),
96
  folder.getUrl(),
97
  photoUrls.length,
98
+ safeText(request.sampleKey),
99
  ]);
100
 
101
  return jsonResponse({
102
  ok: true,
103
  documentationId: request.documentationId,
104
+ sampleKey: safeText(request.sampleKey),
105
+ documentedAt: new Date().toISOString(),
106
  folderUrl: folder.getUrl(),
107
  photoUrls,
108
  });
 
115
  }
116
  }
117
 
118
+ function validateResearcherAccess(accessCode) {
119
  const expected = PropertiesService.getScriptProperties().getProperty("ACCESS_CODE") || "20302030";
120
+ if (String(accessCode || "") !== expected) throw new Error("غير مصرح بالوصول.");
121
+ }
122
+
123
+ function validateSupervisorAccess(accessCode) {
124
+ const expected = PropertiesService.getScriptProperties().getProperty("SUPERVISOR_CODE") || "1448";
125
+ if (String(accessCode || "") !== expected) throw new Error("غير مصرح بالوصول.");
126
  }
127
 
128
  function validateRequest(request) {
129
  if (!request.documentationId) throw new Error("رقم التوثيق مفقود.");
130
+ if (!safeText(request.sampleKey)) throw new Error("مفتاح العينة مفقود.");
131
  if (!safeText(request.researcher)) throw new Error("اسم الباحث مفقود.");
132
  if (!safeText(request.establishmentName)) throw new Error("اسم المنشأة مفقود.");
133
  if (!safeText(request.fieldStatus)) throw new Error("حالة التوثيق مطلوبة.");
134
  if (!safeText(request.statement)) throw new Error("الإفادة الميدانية مطلوبة.");
135
+ if (!Array.isArray(request.photos)) request.photos = [];
136
  if (request.photos.length > CONFIG.maxPhotos) throw new Error("الحد الأعلى 3 صور.");
137
  }
138
 
139
+ function getDocumentationRecords(includeDetails) {
140
+ const sheet = getSheet();
141
+ ensureHeaders(sheet);
142
+ const lastRow = sheet.getLastRow();
143
+ if (lastRow < 2) return [];
144
+
145
+ const values = sheet.getRange(2, 1, lastRow - 1, HEADERS.length).getValues();
146
+ return values
147
+ .filter((row) => row.some((cell) => cell !== ""))
148
+ .map((row) => {
149
+ const date = row[1] instanceof Date ? row[1] : new Date(row[1]);
150
+ const validDate = !Number.isNaN(date.getTime());
151
+ const base = {
152
+ documentationId: safeText(row[0]),
153
+ documentedAt: validDate ? date.toISOString() : "",
154
+ documentedDate: validDate ? Utilities.formatDate(date, CONFIG.timezone, "yyyy-MM-dd") : "",
155
+ researcher: safeText(row[2]),
156
+ establishmentName: safeText(row[3]),
157
+ commercialRecord: safeText(row[4]),
158
+ contractNumber: safeText(row[5]),
159
+ city: safeText(row[6]),
160
+ fieldStatus: safeText(row[7]),
161
+ photoCount: Number(row[11]) || splitUrls(row[9]).length,
162
+ sampleKey: safeText(row[12]),
163
+ };
164
+ if (!includeDetails) return base;
165
+ return Object.assign(base, {
166
+ statement: safeText(row[8]),
167
+ photoUrls: splitUrls(row[9]),
168
+ folderUrl: safeText(row[10]),
169
+ });
170
+ })
171
+ .sort((a, b) => String(b.documentedAt).localeCompare(String(a.documentedAt)));
172
+ }
173
+
174
+ function splitUrls(value) {
175
+ return safeText(value).split(/\s+/).filter((item) => /^https?:\/\//.test(item));
176
+ }
177
+
178
  function getSheet() {
179
  const spreadsheet = SpreadsheetApp.openById(CONFIG.spreadsheetId);
180
  return spreadsheet.getSheetByName(CONFIG.sheetName) || spreadsheet.insertSheet(CONFIG.sheetName);
 
190
  .setBackground("#4137A8")
191
  .setFontColor("#FFFFFF");
192
  sheet.autoResizeColumns(1, HEADERS.length);
193
+ return;
194
  }
195
+ if (!current[12]) sheet.getRange(1, 13).setValue(HEADERS[12]);
196
  }
197
 
198
  function getEvidenceFolder(request) {
 
214
  const bytes = Utilities.base64Decode(String(photo.base64 || ""));
215
  if (bytes.length > CONFIG.maxPhotoBytes) throw new Error("إحدى الصور أكبر من الحد المسموح.");
216
  const extension = mimeType === "image/png" ? "png" : mimeType === "image/webp" ? "webp" : "jpg";
217
+ const timestamp = Utilities.formatDate(new Date(), CONFIG.timezone, "yyyyMMdd-HHmmss");
218
  const name = safeFileName(
219
  `${request.commercialRecord || "دون-سجل"}-${timestamp}-${index + 1}.${extension}`,
220
  );