Bài tập ứng tuyển internship program Q4/2014 – Tính thời gian làm việc không thường xuyên(google apps script, python)

 

Yêu cầu

1 Công ty Startup có nhu cầu tính toán và xem thời gian làm việc của nhân viên. Các nhân viên của công ty làm việc theo giờ giấc linh động và không thường xuyên (irregular working). Họ được phép đăng ký lịch làm việc hàng tháng với sếp và được theo dõi về thời gian làm việc. Sau đó sẽ có quy trình vận hành đảm bảo cuối tháng có được timesheet thời gian làm việc thực tế. Chúng ta cần xây dựng phần mềm để tính toán thời gian làm việc hợp lệ (tức là đã được đăng ký trước)

Để đơn giản hóa chương trình, giả sử công ty chỉ quản lý thời gian với đơn vị là Giờ, không quan tâm phút, giây.

Input sheet format:

Lịch đăng ký làm việcThời gian làm việc thực tế  được tạo bằng Google Spreadsheets và đặt ở Drive với format tương tự nhau.

Mỗi sheet là dữ liệu thời gian 1 nhân viên. Tên sheet = tên nhân viên

Có 3 cột: Day – Ngày làm việc, From – Làm từ mấy giờ, To – Làm đến mấy giờ

clip_image002clip_image004

Yêu cầu kỹ thuật:

Sử dụng Google app script để làm web service trả 2 dữ liệu input cho client

Viết console client tính toán bằng 1 trong các ngôn ngữ: C/C++/C#/Python/Ruby/Golang

Kết quả báo cáo giao diện có thể dùng giải pháp render HTML/XPS/RDLC và mở viewer tương ứng

Thời gian thực hiện

3 ngày

Giải:

Console client chọn python; render Html

clip_image006

1/ Implement App script:

function getDatasources(ssSchedulerId, ssAttendanceId) {
  var ssScheduler = SpreadsheetApp.openById(ssSchedulerId);
  var ssAttendance= SpreadsheetApp.openById(ssAttendanceId);
  var shSchedulers = ssScheduler.getSheets();
  var shAttendances = ssAttendance.getSheets();
  var dataSchedulers = shSchedulers.map(function(sheet) {
    return {
      name: sheet.getName(),
      data: function(sheet){
        var rows = sheet.getDataRange().getValues();
        rows.shift();
        return rows;
      }(sheet)
    };
  });
  var dataAttendances = shAttendances.map(function(sheet) {
    return {
      name: sheet.getName(),
      data: function(sheet){
        var rows = sheet.getDataRange().getValues();
        rows.shift();
        return rows;
      }(sheet)
    };
  });
  return {
    dataSchedulers: dataSchedulers,
    dataAttendances: dataAttendances
  };
}

 

2/ Deploy as Api executable và lấy App Id

3/ Vào google developer console để enable app script api, tạo Oauth client id

4/ Viết python console để test server

def get_credentials():
	home_dir = os.path.expanduser('~')
	credential_dir = os.path.join(home_dir, '.credentials')
	if not os.path.exists(credential_dir):
		os.makedirs(credential_dir)
	credential_path = os.path.join(credential_dir, script-python-quickstart.json')

	store = oauth2client.file.Storage(credential_path)
	credentials = store.get()
	if not credentials or credentials.invalid:
		flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
		flow.user_agent = APPLICATION_NAME
		if flags:
			credentials = tools.run_flow(flow, store, flags)
		else: # Needed only for compatibility with Python 2.6
			credentials = tools.run(flow, store)
		print('Storing credentials to ' + credential_path)
	return credentials

SCRIPT_ID = '<app_id or script_id from deployed app script>'

sheetSchedulerId = '<id from spreadsheet url>'
sheetAttendanceId = '<id from spreadsheet url>'
request = {
	"function": "getDatasources",
	"parameters": [sheetSchedulerId, sheetAttendanceId],
	"devMode": True
}
credentials = get_credentials()
http = credentials.authorize(httplib2.Http())
service = discovery.build('script', 'v1', http=http)
response = service.scripts().run(body=request, scriptId=SCRIPT_ID).execute()
dataSchedulers = response['response']['result'].get('dataSchedulers')
dataAttendances = response['response']['result'].get('dataAttendances')
print("{0}".format(dataSchedulers).encode(sys.stdout.encoding, errors='replace'))
print("{0}".format(dataAttendances).encode(sys.stdout.encoding, errors='replace'))

5/ Viết xử lý logic

Ý tưởng: dùng cấu trúc mảng bit để lưu trữ giờ làm việc như hình minh họa sau:

clip_image008

Vậy để lưu trữ lịch làm việc của n nhân viên, 31 ngày, 24 giờ sẽ cần n*31*24 bits tức là cần 1 mảng byte với kích thước n*31*24/8

 

Hàm chuyển từ input object sang bit

DAYS_PER_MONTH = 31
HOURS_PER_DAY = 24
BITS_PER_BYTE = 8
BYTE_PER_DAY = int(HOURS_PER_DAY / BITS_PER_BYTE)
def convert_to_bytearray(dataTime, dicEmployees):
	bitaResult = bytearray(len(dataSchedulers) * DAYS_PER_MONTH * BYTE_PER_DAY)
	for eidx, emp in enumerate(dataSchedulers):
		rows = emp.get('data')
		name = emp.get('name')
		empOffset = dicEmployees[name]
		for row in rows:
			#print("{0}\t{1}\n".format(emp.get('name').encode(sys.stdout.encoding, errors='replace'), row))
			dayOffset = ((row[0] - 1) % DAYS_PER_MONTH) * BYTE_PER_DAY
			u1 = int(row[1] / BITS_PER_BYTE)
			u2 = int(row[2] / BITS_PER_BYTE)
			v1 = (u1 + 1) * BITS_PER_BYTE - row[1]
			v2 = row[2] - u2 * BITS_PER_BYTE
			if u1==u2 :
				#truong hop: 2 ngay cung nam tren 1 khoang
				bitaResult[ empOffset + dayOffset + u1 ] = ((1 << v1) - 1) & (~0 << (BITS_PER_BYTE - v2))
			else:
				#bat v1 bit phai cua byte thu u1
				bitaResult[ empOffset + dayOffset + u1 ] = (1 << v1) - 1

				#bat cac bit giua len 1
				for u in range(u1+1 ,u2):
					bitaResult[ empOffset + dayOffset + u ] = 1

				#bat v2 bit trai cua byte thu u2
				bitaResult[ empOffset + dayOffset + u2 ] = 0xFF & (0xFF << (BITS_PER_BYTE - v2))
	return bitaResult
....
dicEmployees = {}
for eidx, emp in enumerate(dataSchedulers):
	name = emp.get('name')
	empOffset = DAYS_PER_MONTH * BYTE_PER_DAY * eidx
	dicEmployees[name] = empOffset
bitaSchedulers = convert_to_bytearray(dataSchedulers, dicEmployees)
bitaAttendances = convert_to_bytearray(dataAttendances, dicEmployees)

Để tính toán kết quả chỉ cần dùng phép AND 2 dãy bit đầu vào

bitaResult = bytearray([a & b for a,b in zip(bitaSchedulers, bitaAttendances)])

6/ Xây dựng html template bằng MakoTemplate

 

Tạo file result.tmpl.html

timei - ${title}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
table {
	/*border-collapse: collapse;*/
	border-spacing: 1px;
}
.outer-table{
	overflow-x: scroll;
}
td{
	white-space: nowrap;
	min-width: ${len(dicEmployees) * 30 + 5}px;
}
td > div{
	width: 30px;
	height: 30px;
	float: left;
	background-color: #ddd;
}

td > span{
	width: 0;
    height: 0;
    display: block;
    top: -25px;
    position: relative;
}
li{
	float: left;
    margin-right: 20px;
    line-height: 15px;
}
li > span{
	width: 60px;
    height: 15px;
    border: 1px solid #aaa;
    display: block;
    float: left;
    margin-right: 10px;
}
% for idx, color in enumerate(['#6696ba','#e2e38b','#e7a553','#7e4b68','#292965','#8d0f0f','#618d46','#ffadad']):
li:nth-child(${idx + 1}) > span,td > div:nth-child(${idx + 1}).on{
	background-color: ${color};
}
% endfor
<ul>
	% for emp_name in dicEmployees:
	<li><span></span> ${ emp_name }</li>
	% endfor
</ul>
<div class="outer-table" style="clear:both;">
<table>
	<thead>
		<tr>
			<th></th>
			% for day in range(1,DAYS_PER_MONTH):
			<th>
			${day}
			</th>
			% endfor
		</tr>
	</thead>
	<tbody>
		% for hour in range(0,HOURS_PER_DAY):
		<tr>
			<td style="min-width:25px;"><span>${hour}</span></td>
			% for day in range(1,DAYS_PER_MONTH):
			<td>
				% for emp_name in dicEmployees:
				&lt;%	
					empOffset = dicEmployees[emp_name]
					dayOffset = ((day - 1) % DAYS_PER_MONTH) * BYTE_PER_DAY
					hourOffset = int(hour / BITS_PER_BYTE)
					offset = (hourOffset + 1) * BITS_PER_BYTE - hour
					is_attended = 'on' if (bitaResult[empOffset + dayOffset + hourOffset] &amp; (1&lt;
				<div class="${is_attended}"></div>
				% endfor
			</td>
			% endfor
		</tr>
		% endfor
	</tbody>
</table>
</div>

7/ Gọi từ trình duyệt chrome (windows) sau khi lưu kết quả render vào file

mytemplate = Template(filename='result.tmpl.html', output_encoding='utf-8', encoding_errors='replace')
szRender = mytemplate.render(title="Mar. 2016 report",dicEmployees=dicEmployees,bitaResult=bitaResult)
f = open('result.html', 'wb+')
f.write(szRender)
url = pathlib.Path(os.getcwd() + '\\' + f.name).as_uri()
chrome_path = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
webbrowser.get(chrome_path).open_new(url)

 

2016-04-03 05_33_28-BaiTapDanhGia_270316.pdf - Foxit Reader

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s