Tự động hóa trong quản lý công việc – SEO (có mẫu tham khảo)

Bài viết gốc: https://bytewise.one/vi/p/automation-job-management-seo-reference-template

Bài toán tự động hóa trong quản lý công việc

Việc quản lý một team nói chung, một team content nói riêng luôn khá rắc rối, nhất là khi lượng nhân viên và số lượng công việc càng ngày càng nhiều hơn. Vậy nên việc tìm ra giải pháp tự động hóa công việc quản lý cũng như theo dõi tiến độ công việc là một điều rất cần thiết và quan trọng.

Ví dụ về một luồng công việc

Luồng công việc của mình thường là (mình làm SEO, Marketing, cũng có làm chút Code Website, viết giao diện, tạo chức năng thêm cho Website, viết vài tools vớ vẩn cho đỡ buồn hoặc giúp giải quyết mấy công việc nhàm chán):

  1. Nghiên cứu từ khóa
  2. Đưa từ khóa cho content viết
  3. Content viết thì sẽ yêu cầu mình đọc và duyệt bài, nếu có vấn đề gì thì sẽ note lại để content sửa
  4. Content sửa bài xong thì mình sẽ đánh dấu là ok
  5. Những bài viết nào ok rồi thì sẽ đăng, submit Google Search Console
  6. Đăng bài và theo dõi thứ hạng từ khóa, lưu lượng truy cập (đối với từng url)
  7. Sau một khoảng thời gian nhất định, tổng kết thống kê xem những bài viết nào đạt được nhiều lưu lượng truy cập nhất, và những bài đó được ai viết. Và ai là người viết hiệu quả nhất, đem về nhiều khách truy cập nhất.

Tự động hóa các công việc lặp lại, tại sao không?

Cơ bản thì luồng công việc nó như vậy, nên mình nghĩ ngay, liệu có cách nào tự động hóa những công việc lặp đi lặp lại nhàm chán hay không? 

Có một số việc có thể tối ưu giúp đỡ tốn thời gian hơn, dễ theo dõi hơn cũng như đỡ tốn công sức hơn. Ví dụ như khi content viết xong, thì sau đó sẽ cần thông báo với mình, để mình vào check nội dung, rồi sau khi duyệt có vấn đề gì, thì lại note lại kêu content vô sửa. Cứ thế đến lúc hài lòng với content thì mình sẽ check ok cho bài viết, để đó sau đăng. 

Nghe quy trình khá là loằng ngoằng, và khá tốn thời gian, nếu cứ nhắn qua nhắn lại như thế cũng mất nhiều thời gian, chưa kể có thể bị trôi tin nhắn, quên, vậy nên có hệ thống tự động hỗ trợ là tốt nhất, nhanh chóng và hiệu quả!

Hơn nữa, việc thống kê kết quả, hiệu quả công việc thì nếu mỗi lần cần báo cáo mới export dữ liệu hoặc nhập liệu tay, thì khá là “nông dân” và tốn công sức, tốn thời gian nữa. Mục tiêu là tự động hóa hoàn toàn những cái gì tự động được, nhất là những thứ lặp đi lặp lại.

Các phần công việc có thể tự động hóa

Dựa theo cái luồng công việc như trên, chúng ta thấy rằng có mấy điểm tự động và quản lý bằng Google Sheets được:

  • Việc yêu cầu duyệt bài
  • Việc duyệt bài và thông báo đã duyệt
  • Dựa theo dữ liệu từ bản Google Sheets quản lý (gồm có url đăng, người viết), và Google Analytics, đưa ra số liệu về traffic của từng bài viết, và của từng người viết bài trong một khoảng thời gian nhất định.

Làm như thế nào?

Bài toán cụ thể

Như luồng công việc đã được mình nêu ra ở phần trên, thì sẽ cần giải quyết một số vấn đề sau:

  • Một bảng tính chung, để có thể làm việc nhóm và nhập dữ liệu một cách đơn giản và hiệu quả, với các cột dữ liệu được điền rõ ràng, đúng mục đích, không bị trùng lặp (Phần này có mẫu Google Sheets sẵn)
  • Tự động gửi Email (tới email của mình) khi có ai đó click vào checkbox yêu cầu duyệt bài viết.
  • Tự động gửi Email (tới người yêu cầu duyệt bài), khi mình duyệt bài ok và click vào checkbox đã duyệt bà
  • Ngoài ra, có thể định kỳ hàng tuần, quét KPI, nếu KPI chưa đạt thì gửi Email nhắc nhở. Cuối tuần gửi Email tổng kết tỉ lệ hoàn thành công việc.
  • Kết nối với Data Studio, giờ đã đổi tên thành Looker Studio, không rõ vì lý do gì ┐ (‘~ `;) ┌, và tạo báo cáo từ nó

Tất nhiên, sẽ phát sinh một số thứ mình cần quan tâm, như phân quyền trong Bảng tính làm việc chung, một số cột thì chỉ có chính mình có quyền sửa, ví dụ cột duyệt bài, không ai khác có quyền sửa cột này. 

=> Vì nếu nhân viên click vào tự duyệt thì hỏng \ (〇_o) /

Việc phân quyền và khóa quyền cho các vùng của bảng tính cũng khá dễ, bạn có thể xem mẫu mình đã làm ở phía dưới, hoặc tự phân quyền bằng chức năng Bảo vệ bảng tính và ô của Google Sheets. Không khó để làm, chỉ hơi mất công cài đặt ban đầu thôi.

À, ngoài ra, còn một số chức năng nho nhỏ nữa, cũng không phải chức năng quan trọng, nhưng chỉ một chút thoải mái nho nhỏ cũng sẽ làm ngày dài của bạn trở nên vui vẻ hơn, khỏe khoắn hơn (✧ω✧)

Chức năng đó là gì, để mình nói rõ hơn tình huống… 

Chuyện là, như ảnh ở dưới, khi mình click vào ô Status, tích rồi  tức là đã duyệt bài, thì cũng cần điền luôn vào ô ở cột End, nghĩa là đã duyệt bài vào ngày này tháng này năm này.

Điền một vài cái thì cũng không sao, NHƯNG, điền nhiều điền mãi cũng là vấn đề đó. Tui lười 凸 (`ロ ´) 凸. Hơn nữa, một trong những triết lý mà mình theo đuổi, đó là cái gì lười được thì lười =)))). Và, cái gì có bàn tay con người nhập liệu thủ công vào thì sẽ luôn có khả năng có sai lầm, nên để máy nó làm hộ cho mình cho lành.

Vậy nên, mình đã code đơn giản một chức năng, để khi click vào ô Status, thì ô End cũng được tự động điền ngày hôm nay vào, dưới định dạng YYYY-MM-DD (năm-tháng-ngày). Ở bản này mình cũng điền luôn cái ô Deadline đúng ngày như ô End. Nghe có vẻ hơi lạ đúng không?

Thực ra làm vậy để phục vụ báo cáo ở Looker Studio cho tiện đó. Lọc theo Deadline để biết được theo tuần đã có bao nhiêu bài hoàn thành rồi. Như vậy tiện hơn và nhanh hơn, đỡ lằng nhằng.

Thôi, có lẽ nói nhiều quá rồi, chúng ta bắt đầu làm nào!

Bước 1: Tạo một bảng tính để làm việc chung giữa các team members

Bước này thì bạn cần tạo một file Google Sheets, với các cột dữ liệu để các thành viên trong team có thể làm việc cùng nhau, nhớ phân quyền edit các cột một cách cẩn thận, khóa quyền những cột quan trọng, ví dụ như những cột duyệt bài, hoặc các cột về từ khóa (đưa từ khóa xong nhân viên thấy từ khóa khó viết quá, sửa lại từ khóa thì … 凸 ( ̄ ヘ  ̄)

Bạn có thể tham khảo một mẫu mình tạo sẵn: 

https://docs.google.com/spreadsheets/d/1R0mMRT1p-GAv8pTZqwOmNK8FW6mMopexfyTMVwEhkYQ/view

Đối với công việc SEO thì sẽ có một số cột dữ liệu cơ bản như: Keyword, Link Docs, Link Web, Request for article approval, Deadline, End, Status, tùy theo nhu cầu của chính mình mà tạo thôi.

Với mình thì chỉ một số trường dữ liệu trên là quan trọng nhất. Các cái khác để tạo báo cáo được hoàn thiện và dễ nhìn, dễ theo dõi hơn.

Bước 2: Gửi Email tự động bằng hàm

Ở bước này, cái cơ bản là, làm sao để gửi được email =)))))

Rất may, và cũng rất đơn giản, Google Sheets có hỗ trợ cái này. Sử dụng Macro trong App Scripts của Google Sheets, với hàm sau là được 👏

Hàm gửi Email rất đơn giản:

MailApp.sendEmail([email protected], “Subject”, “Message”);

Hoặc nếu thích, bạn có thể viết rõ ràng hơn như sau:

var mail_data = { 

to: [email protected],

subject: ”Your Subject Here,

htmlBody: “HTML Content”,

cc: [email protected]

};

MailApp.sendEmail(mail_data);

Bước 3: Gửi Email khi checkbox được checked

Dưới đây là toàn bộ code giúp cho việc gửi Email đến đúng người đúng thời điểm (ノ ≧ ∀ ≦) ノ ‥… ━━━ ★

Có một số đoạn bạn cần chú ý:

var email_list = {‘User1’: [email protected],

‘User2’: [email protected],

‘User3’: [email protected],

‘User4’: [email protected],

};

Chỗ này sửa lại tên và email cho chuẩn với người viết bài, email của họ để nhận thông báo khi được duyệt bài.

Và email [email protected] là email của người quản lý, sẽ nhận email khi có ai đó yêu cầu duyệt Task, duyệt bài.

Hàm get_quote() chỉ để bớt nhàm chán thôi, cũng không có gì quan trọng. Nó sẽ lấy ngẫu nhiên một câu nói nổi tiếng của ai đó, để chèn vào phần cuối của mail ( ̄ ▽  ̄)

Chú ý, cột C là cột người viết, hoặc người chịu trách nhiệm cho task. Cột M là cột Request for article approval, Cột F là cột Link Docs, có cũng được, không cũng không sao, có thì tiện mình click vào truy cập luôn từ mail, khỏi phải vào Google Sheets kiểm tra.

/** @OnlyCurrentDoc */

function formatDate(date, format, utc) {

var MMMM = [“\x00”, “January”, “February”, “March”, “April”, “May”, “June”, “July”, “August”, “September”, “October”, “November”, “December”];

var MMM = [“\x01”, “Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”];

var dddd = [“\x02”, “Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”];

var ddd = [“\x03”, “Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”];

function ii(i, len) {

var s = i + “”;

len = len || 2;

while (s.length < len) s = “0” + s;

return s;

}

var y = utc ? date.getUTCFullYear() : date.getFullYear();

format = format.replace(/(^|[^\\])yyyy+/g, “$1” + y);

format = format.replace(/(^|[^\\])yy/g, “$1” + y.toString().substr(2, 2));

format = format.replace(/(^|[^\\])y/g, “$1” + y);

 

var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1;

format = format.replace(/(^|[^\\])MMMM+/g, “$1” + MMMM[0]);

format = format.replace(/(^|[^\\])MMM/g, “$1” + MMM[0]);

format = format.replace(/(^|[^\\])MM/g, “$1” + ii(M));

format = format.replace(/(^|[^\\])M/g, “$1” + M);

 

var d = utc ? date.getUTCDate() : date.getDate();

format = format.replace(/(^|[^\\])dddd+/g, “$1” + dddd[0]);

format = format.replace(/(^|[^\\])ddd/g, “$1” + ddd[0]);

format = format.replace(/(^|[^\\])dd/g, “$1” + ii(d));

format = format.replace(/(^|[^\\])d/g, “$1” + d);

var H = utc ? date.getUTCHours() : date.getHours();

format = format.replace(/(^|[^\\])HH+/g, “$1” + ii(H));

format = format.replace(/(^|[^\\])H/g, “$1” + H);

var h = H > 12 ? H12 : H == 0 ? 12 : H;

format = format.replace(/(^|[^\\])hh+/g, “$1” + ii(h));

format = format.replace(/(^|[^\\])h/g, “$1” + h);

var m = utc ? date.getUTCMinutes() : date.getMinutes();

format = format.replace(/(^|[^\\])mm+/g, “$1” + ii(m));

format = format.replace(/(^|[^\\])m/g, “$1” + m);

var s = utc ? date.getUTCSeconds() : date.getSeconds();

format = format.replace(/(^|[^\\])ss+/g, “$1” + ii(s));

format = format.replace(/(^|[^\\])s/g, “$1” + s);

var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds();

format = format.replace(/(^|[^\\])fff+/g, “$1” + ii(f, 3));

f = Math.round(f / 10);

format = format.replace(/(^|[^\\])ff/g, “$1” + ii(f));

f = Math.round(f / 10);

format = format.replace(/(^|[^\\])f/g, “$1” + f);

var T = H < 12 ? “AM” : “PM”;

format = format.replace(/(^|[^\\])TT+/g, “$1” + T);

format = format.replace(/(^|[^\\])T/g, “$1” + T.charAt(0));

var t = T.toLowerCase();

format = format.replace(/(^|[^\\])tt+/g, “$1” + t);

format = format.replace(/(^|[^\\])t/g, “$1” + t.charAt(0));

var tz = –date.getTimezoneOffset();

var K = utc || !tz ? “Z” : tz > 0 ? “+” : “-“;

if (!utc) {

tz = Math.abs(tz);

var tzHrs = Math.floor(tz / 60);

var tzMin = tz % 60;

K += ii(tzHrs) + “:” + ii(tzMin);

}

format = format.replace(/(^|[^\\])K/g, “$1” + K);

var day = (utc ? date.getUTCDay() : date.getDay()) + 1;

format = format.replace(new RegExp(dddd[0], “g”), dddd[day]);

format = format.replace(new RegExp(ddd[0], “g”), ddd[day]);

format = format.replace(new RegExp(MMMM[0], “g”), MMMM[M]);

format = format.replace(new RegExp(MMM[0], “g”), MMM[M]);

format = format.replace(/\\(.)/g, “$1”);

return format;

};

function get_datestring_today() {

return formatDate(new Date(), ” dd-MM-yyyy hh:mmtt”);

}

function sendNotification(e) {

var email_list = {‘User1’: [email protected],

‘User2’: [email protected],

‘User3’: [email protected],

‘User4’: [email protected],

};

var range = e.range;

var cell_value = range.getValue().toString();

var col_change_name = range.getA1Notation().toString().match(/[a-zA-Z]+/)[0];

var row_change_index = range.getA1Notation().toString().match(/\d+/)[0];

var assigned_people = SpreadsheetApp.getActiveSpreadsheet().getRange(“C”+row_change_index).getValue();

var link_doc = SpreadsheetApp.getActiveSpreadsheet().getRange(“F”+row_change_index).getValue();

var date_string = get_datestring_today();

var email_to_send = email_list[assigned_people];

if (cell_value.toLowerCase() == ‘true’ && col_change_name.toLowerCase() == ‘m’) {

var mess = “Request for article approval from “ + assigned_people + ” link docs “ + link_doc;

MailApp.sendEmail([email protected], date_string + ” – Notification from Website Content Management”, mess);

}

if (cell_value.toLowerCase() == ‘true’ && col_change_name.toLowerCase() == ‘n’) {

var quote = get_quote();

var mess = “<p>DONE! Your article has been approved, “ + assigned_people + “!</p>” + “<p>Try not to get caught up in the deadline, okay!!</p><p>Link docs: “ + link_doc + “</p><p>” + quote[‘content’] + “<br/>Author: <strong>”+ quote[‘author’] +“</strong></p>”;

var mail_data = { to: email_to_send,

subject: date_string + ” – Notification from Website Content Management”,

htmlBody: mess,

cc: [email protected]

};

MailApp.sendEmail(mail_data);

}

}

function get_quote() {

var response = UrlFetchApp.fetch(“https://api.quotable.io/random”);

var quote_data = JSON.parse(response.getContentText());

return quote_data;

}

 

 

 

Bước 4: Định kỳ hàng tuần, quét và kiểm tra tỉ lệ hoàn thành công việc, sau đó gửi thông báo tới các thành viên trong nhóm

Chức năng này khá hữu ích nè. Thay vì bạn phải ngồi dò từng người một đã làm được bao nhiêu công việc rồi, tỉ lệ hoàn thành tổng là bao nhiêu, thì giờ bạn chỉ cần ngồi đợi tới giờ là có báo cáo thôi.

Mỗi đầu tuần / cuối tuần sẽ có mail như sau gửi tới toàn bộ nhân sự trong team:

Phần code để chạy được báo cáo hàng tuần như sau, bạn có thể xem ở trong file bảng tính mẫu mình cung cấp, hoặc copy paste đoạn mã sau vào App Script và tự setup.

Chú ý thay một số chỗ để hàm chạy được đúng nhé, ví dụ thay cái tên User cho chuẩn, hoặc có thể thêm hàm gửi Email tới cả những thành viên trong nhóm. Hàm dưới chỉ gửi cho chính mình thôi! Thêm vào phần cc email là được, mỗi email cách nhau bằng dấu phẩy là xong, rất đơn giản!

function get_current_month() {

let today = new Date();

let month = today.getMonth()+1;

return month;

}

function formatDate(date, format, utc) {

let MMMM = [“\x00”, “January”, “February”, “March”, “April”, “May”, “June”, “July”, “August”, “September”, “October”, “November”, “December”];

let MMM = [“\x01”, “Jan”, “Feb”, “Mar”, “Apr”, “May”, “Jun”, “Jul”, “Aug”, “Sep”, “Oct”, “Nov”, “Dec”];

let dddd = [“\x02”, “Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”];

let ddd = [“\x03”, “Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”];

function ii(i, len) {

let s = i + “”;

len = len || 2;

while (s.length < len) s = “0” + s;

return s;

}

let y = utc ? date.getUTCFullYear() : date.getFullYear();

format = format.replace(/(^|[^\\])yyyy+/g, “$1” + y);

format = format.replace(/(^|[^\\])yy/g, “$1” + y.toString().substr(2, 2));

format = format.replace(/(^|[^\\])y/g, “$1” + y);

let M = (utc ? date.getUTCMonth() : date.getMonth()) + 1;

format = format.replace(/(^|[^\\])MMMM+/g, “$1” + MMMM[0]);

format = format.replace(/(^|[^\\])MMM/g, “$1” + MMM[0]);

format = format.replace(/(^|[^\\])MM/g, “$1” + ii(M));

format = format.replace(/(^|[^\\])M/g, “$1” + M);

let d = utc ? date.getUTCDate() : date.getDate();

format = format.replace(/(^|[^\\])dddd+/g, “$1” + dddd[0]);

format = format.replace(/(^|[^\\])ddd/g, “$1” + ddd[0]);

format = format.replace(/(^|[^\\])dd/g, “$1” + ii(d));

format = format.replace(/(^|[^\\])d/g, “$1” + d);

let H = utc ? date.getUTCHours() : date.getHours();

format = format.replace(/(^|[^\\])HH+/g, “$1” + ii(H));

format = format.replace(/(^|[^\\])H/g, “$1” + H);

let h = H > 12 ? H12 : H == 0 ? 12 : H;

format = format.replace(/(^|[^\\])hh+/g, “$1” + ii(h));

format = format.replace(/(^|[^\\])h/g, “$1” + h);

let m = utc ? date.getUTCMinutes() : date.getMinutes();

format = format.replace(/(^|[^\\])mm+/g, “$1” + ii(m));

format = format.replace(/(^|[^\\])m/g, “$1” + m);

let s = utc ? date.getUTCSeconds() : date.getSeconds();

format = format.replace(/(^|[^\\])ss+/g, “$1” + ii(s));

format = format.replace(/(^|[^\\])s/g, “$1” + s);

let f = utc ? date.getUTCMilliseconds() : date.getMilliseconds();

format = format.replace(/(^|[^\\])fff+/g, “$1” + ii(f, 3));

f = Math.round(f / 10);

format = format.replace(/(^|[^\\])ff/g, “$1” + ii(f));

f = Math.round(f / 10);

format = format.replace(/(^|[^\\])f/g, “$1” + f);

let T = H < 12 ? “AM” : “PM”;

format = format.replace(/(^|[^\\])TT+/g, “$1” + T);

format = format.replace(/(^|[^\\])T/g, “$1” + T.charAt(0));

let t = T.toLowerCase();

format = format.replace(/(^|[^\\])tt+/g, “$1” + t);

format = format.replace(/(^|[^\\])t/g, “$1” + t.charAt(0));

let tz = –date.getTimezoneOffset();

let K = utc || !tz ? “Z” : tz > 0 ? “+” : “-“;

if (!utc) {

tz = Math.abs(tz);

let tzHrs = Math.floor(tz / 60);

let tzMin = tz % 60;

K += ii(tzHrs) + “:” + ii(tzMin);

}

format = format.replace(/(^|[^\\])K/g, “$1” + K);

let day = (utc ? date.getUTCDay() : date.getDay()) + 1;

format = format.replace(new RegExp(dddd[0], “g”), dddd[day]);

format = format.replace(new RegExp(ddd[0], “g”), ddd[day]);

format = format.replace(new RegExp(MMMM[0], “g”), MMMM[M]);

format = format.replace(new RegExp(MMM[0], “g”), MMM[M]);

format = format.replace(/\\(.)/g, “$1”);

return format;

};

function get_datestring_today() {

return formatDate(new Date(), ” dd-MM-yyyy hh:mmtt”);

}

function get_lastday_of_month(month=“”) {

let today = new Date();

let d_month = today.getMonth()+1;

if (month != “”) d_month = parseInt(month);

let d = new Date(today.getFullYear(), d_month, 0);

return formatDate(d, “yyyy-MM-dd”);

}

function get_firstday_of_month(month=“”) {

let today = new Date();

let d_month = today.getMonth();

if (month != “”) d_month = parseInt(month)-1;

let d = new Date(today.getFullYear(), d_month, 1);

return formatDate(d, “yyyy-MM-dd”);

}

function get_deadline_num(month=8) {

let last_day_of_month = get_lastday_of_month(month);

let first_day_of_month = get_firstday_of_month(month);

let data_deadlines = {

‘User1’: 0,

‘User2’: 0,

‘User3’: 0,

};

let data_end_dates = {

‘User1’: 0,

‘User2’: 0,

‘User2’: 0,

};

 

let sheet = SpreadsheetApp.getActive().getSheetByName(‘Content Website’);

let deadlines = sheet.getRange(‘L:L’).getValues();

let assigns = sheet.getRange(‘C:C’).getValues();

let keywords = sheet.getRange(‘A:A’).getValues();

let end_dates = sheet.getRange(‘K:K’).getValues();

deadlines.forEach((value, index) => {

let row_date = new Date(value);

if ( (index != 0) && ( row_date <= new Date(last_day_of_month) ) && ( row_date >= new Date(first_day_of_month)) ) {

data_deadlines[assigns[index]] += 1;

}

});

 

end_dates.forEach((value, index) => {

let row_date = new Date(value);

if ( (index != 0) && (value != “”) && (new Date(first_day_of_month) <= row_date) && (row_date <= new Date(last_day_of_month)) && (row_date <= new Date(deadlines[index]) )) {

data_end_dates[assigns[index]] += 1;

}

});

let kpi_str = “”;

 

for (let index in data_end_dates) {

let value = data_end_dates[index];

kpi_str += “<p style=’font-size:16px;’><strong>” + index + “</strong>: “ + value + “/” + data_deadlines[index] + ” <span style=’color:red;font-weight:700;’>(“ + 100*parseFloat(value/data_deadlines[index]).toFixed(2) +‘%)</span></p>’;

}

Logger.log(kpi_str);

return kpi_str;

}

 

 

function warning_kpi_manually() {

let month = get_current_month();

let kpi_str = get_deadline_num(month);

let date_string = get_datestring_today();

let mess = “<p>Monthly KPI Statistics for SEO Articles in “ + month + “. Everyone, please pay attention!</p>” + kpi_str;

MailApp.sendEmail({ to: [email protected],

subject: “Monthly KPI: “ + month + ” – Sent “ + date_string,

htmlBody: mess,

cc: [email protected]});

}

Bạn chỉ cần cài đặt để khi nào chạy chương trình là ok. Mình thường để chạy vào đầu tuần và cuối tuần, để có số liệu hàng tuần, theo dõi được hiệu quả công việc của cả Team đang như thế nào.

 

 

Chức năng tự động điền ngày

Sử dụng hàm sau là được, khá đơn giản. Chú ý Cột K là cột End, cột L là cột Deadline, công cụ sẽ điền vào 2 cột này một cách tự động khi ô ở cột N tương ứng được checked. Cột N là cột Status!

function add_date_when_status_ok(e) {

var range = e.range;

var date = new Date();

var day = date.getDate();

if (day < 10) day = “0” + day;

var month = date.getMonth()+1;

if (month < 10) month = “0” + month;

var today_date = date.getFullYear() + ‘-‘ + month + ‘-‘ + day;

var cell_value = range.getValue().toString();

var col_change_name = range.getA1Notation().toString().match(/[a-zA-Z]+/)[0];

var row_change_index = range.getA1Notation().toString().match(/\d+/)[0];

if (cell_value.toLowerCase() == ‘true’ && col_change_name.toLowerCase() == ‘n’) {

// FILL END DATE

SpreadsheetApp.getActiveSpreadsheet().getRange(“K”+row_change_index).setValue(today_date);

// FIX DEADLINE DATE

SpreadsheetApp.getActiveSpreadsheet().getRange(“L”+row_change_index).setValue(today_date);

}

}

Code cũng không có gì khó khăn, đúng không nào, đầu tiên chúng ta khởi tạo một số biến, để có thể tạo ra được `today_date` với định dạng YYYY-MM-DD (năm-tháng-ngày). Giá trị này nhằm mục đích điền vào các ô End và Deadline.

 

Chúng ta sẽ thêm Trigger để hàm này luôn chạy khi có ai chỉnh sửa bảng tính. Vậy nên hàm này sẽ kiểm tra xem ô được sửa có phải ô Status không, và giá trị được chuyển từ False thành True hay không. Nếu thỏa mãn các điều trên, tool sẽ tự động điền giá trị ngày hiện tại vào các ô chúng ta muốn.

Chú ý thêm Trigger ở App Scripts nhé, Trigger là chạy function trên mỗi khi có ai đó Edit bảng tính.

Mẫu tham khảo

Mẫu Google Sheets quản lý công việc cho SEO – dùng cho quản lý content (có thể sửa lại để phù hợp với nhu cầu): https://docs.google.com/spreadsheets/d/1R0mMRT1p-GAv8pTZqwOmNK8FW6mMopexfyTMVwEhkYQ/view

Mẫu Looker Studio sử dụng nguồn dữ liệu từ bản Google Sheets trên và Google Analytics (GA4) làm báo cáo: Sẽ cập nhật sau!

Một số ảnh demo

 

 

2023-07-05: Viết những nội dung đầu tiên cho bài viết, với khung bài viết cơ bản

2023-07-05: Bổ sung tiếp phần gửi email thông báo KPI của cả team

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *