xrono 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (520) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +20 -0
  4. data/Gemfile +30 -0
  5. data/Gemfile.lock +336 -0
  6. data/LICENSE +21 -0
  7. data/README.md +64 -0
  8. data/RELEASE_NOTES.md +83 -0
  9. data/Rakefile +15 -0
  10. data/XRONO.md +64 -0
  11. data/app/assets/images/chosen-sprite.png +0 -0
  12. data/app/assets/images/grid.png +0 -0
  13. data/app/assets/images/isotope_logo.png +0 -0
  14. data/app/assets/images/logo.png +0 -0
  15. data/app/assets/images/logo_122x40.png +0 -0
  16. data/app/assets/images/logo_97x40.png +0 -0
  17. data/app/assets/images/main_bg.jpg +0 -0
  18. data/app/assets/images/nav_bg.jpg +0 -0
  19. data/app/assets/images/rails.png +0 -0
  20. data/app/assets/images/top_bg.jpg +0 -0
  21. data/app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  22. data/app/assets/images/ui-bg_flat_0_fbf9ee_40x100.png +0 -0
  23. data/app/assets/images/ui-bg_flat_55_fbec88_40x100.png +0 -0
  24. data/app/assets/images/ui-bg_flat_65_ffffff_40x100.png +0 -0
  25. data/app/assets/images/ui-bg_flat_75_cccccc_40x100.png +0 -0
  26. data/app/assets/images/ui-bg_flat_75_dadada_40x100.png +0 -0
  27. data/app/assets/images/ui-bg_flat_75_e6e6e6_40x100.png +0 -0
  28. data/app/assets/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  29. data/app/assets/images/ui-bg_flat_95_fef1ec_40x100.png +0 -0
  30. data/app/assets/images/ui-bg_glass_75_d0e5f5_1x400.png +0 -0
  31. data/app/assets/images/ui-bg_glass_85_dfeffc_1x400.png +0 -0
  32. data/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  33. data/app/assets/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png +0 -0
  34. data/app/assets/images/ui-bg_inset-hard_100_f5f8f9_1x100.png +0 -0
  35. data/app/assets/images/ui-bg_inset-hard_100_fcfdfd_1x100.png +0 -0
  36. data/app/assets/images/ui-icons_217bc0_256x240.png +0 -0
  37. data/app/assets/images/ui-icons_222222_256x240.png +0 -0
  38. data/app/assets/images/ui-icons_2e83ff_256x240.png +0 -0
  39. data/app/assets/images/ui-icons_454545_256x240.png +0 -0
  40. data/app/assets/images/ui-icons_469bdd_256x240.png +0 -0
  41. data/app/assets/images/ui-icons_6da8d5_256x240.png +0 -0
  42. data/app/assets/images/ui-icons_888888_256x240.png +0 -0
  43. data/app/assets/images/ui-icons_cd0a0a_256x240.png +0 -0
  44. data/app/assets/images/ui-icons_d8e7f3_256x240.png +0 -0
  45. data/app/assets/images/ui-icons_f9bd01_256x240.png +0 -0
  46. data/app/assets/javascripts/add_taxonomy.js.erb +307 -0
  47. data/app/assets/javascripts/admin_invoices.js +15 -0
  48. data/app/assets/javascripts/api/v1/clients.js.coffee +3 -0
  49. data/app/assets/javascripts/api/v1/tokens.js.coffee +3 -0
  50. data/app/assets/javascripts/application.js.erb +25 -0
  51. data/app/assets/javascripts/bootstrap-dropdown.js +92 -0
  52. data/app/assets/javascripts/bootstrap-tabs.js +80 -0
  53. data/app/assets/javascripts/bootstrap.js.coffee +4 -0
  54. data/app/assets/javascripts/calendar.js +25 -0
  55. data/app/assets/javascripts/chosen.jquery.js +898 -0
  56. data/app/assets/javascripts/client_login/clients.js.coffee +3 -0
  57. data/app/assets/javascripts/client_login/reports.js.coffee +3 -0
  58. data/app/assets/javascripts/comments.js +16 -0
  59. data/app/assets/javascripts/css_browser_selector.js +8 -0
  60. data/app/assets/javascripts/dashboard.js +3 -0
  61. data/app/assets/javascripts/data_vaults.js.coffee +3 -0
  62. data/app/assets/javascripts/file_attachments.js +9 -0
  63. data/app/assets/javascripts/jquery-tagselector.js +50 -0
  64. data/app/assets/javascripts/jquery-ui-1.8.16.custom.min.js +791 -0
  65. data/app/assets/javascripts/jquery.jeditable.js +543 -0
  66. data/app/assets/javascripts/jquery_ujs.js +336 -0
  67. data/app/assets/javascripts/schedule_modal.js +25 -0
  68. data/app/assets/javascripts/specify_project.js +21 -0
  69. data/app/assets/javascripts/ticket_board.js +104 -0
  70. data/app/assets/stylesheets/_jquery-ui-1.8.16.custom.css +568 -0
  71. data/app/assets/stylesheets/application.css +7 -0
  72. data/app/assets/stylesheets/bootstrap_and_overrides.css.scss +41 -0
  73. data/app/assets/stylesheets/chosen.css +367 -0
  74. data/app/assets/stylesheets/client_login/clients.css.scss +3 -0
  75. data/app/assets/stylesheets/client_login/reports.css.scss +3 -0
  76. data/app/assets/stylesheets/data_vaults.css.scss +3 -0
  77. data/app/assets/stylesheets/jquery-tagselector.css +49 -0
  78. data/app/assets/stylesheets/responsive.scss +650 -0
  79. data/app/assets/stylesheets/screen.scss +303 -0
  80. data/app/controllers/admin/base_controller.rb +18 -0
  81. data/app/controllers/admin/invoices_controller.rb +42 -0
  82. data/app/controllers/admin/payroll_controller.rb +29 -0
  83. data/app/controllers/admin/site_settings_controller.rb +19 -0
  84. data/app/controllers/admin/unentered_time_report_controller.rb +26 -0
  85. data/app/controllers/admin/users_controller.rb +86 -0
  86. data/app/controllers/admin/weekly_time_report_controller.rb +11 -0
  87. data/app/controllers/api/base_controller.rb +14 -0
  88. data/app/controllers/api/v1/base_controller.rb +2 -0
  89. data/app/controllers/api/v1/clients_controller.rb +10 -0
  90. data/app/controllers/api/v1/credentials_controller.rb +10 -0
  91. data/app/controllers/api/v1/projects_controller.rb +27 -0
  92. data/app/controllers/api/v1/tickets_controller.rb +46 -0
  93. data/app/controllers/api/v1/tokens_controller.rb +30 -0
  94. data/app/controllers/api/v1/work_units_controller.rb +23 -0
  95. data/app/controllers/client_login/base_controller.rb +3 -0
  96. data/app/controllers/client_login/clients_controller.rb +35 -0
  97. data/app/controllers/client_login/contacts_controller.rb +53 -0
  98. data/app/controllers/client_login/projects_controller.rb +24 -0
  99. data/app/controllers/client_login/reports_controller.rb +23 -0
  100. data/app/controllers/client_login/tickets_controller.rb +41 -0
  101. data/app/controllers/client_login/work_units_controller.rb +11 -0
  102. data/app/controllers/clients_controller.rb +103 -0
  103. data/app/controllers/comments_controller.rb +75 -0
  104. data/app/controllers/contacts_controller.rb +54 -0
  105. data/app/controllers/dashboard/base_controller.rb +79 -0
  106. data/app/controllers/data_vaults_controller.rb +12 -0
  107. data/app/controllers/doorkeeper/application_controller.rb +33 -0
  108. data/app/controllers/file_attachments_controller.rb +60 -0
  109. data/app/controllers/projects_controller.rb +62 -0
  110. data/app/controllers/tickets_controller.rb +48 -0
  111. data/app/controllers/users_controller.rb +57 -0
  112. data/app/controllers/work_units_controller.rb +114 -0
  113. data/app/controllers/xrono/application_controller.rb +65 -0
  114. data/app/helpers/admin/unentered_time_report_helper.rb +9 -0
  115. data/app/helpers/admin_base_helper.rb +14 -0
  116. data/app/helpers/clients_helper.rb +5 -0
  117. data/app/helpers/dashboard_helper.rb +9 -0
  118. data/app/helpers/gravatar_helper.rb +5 -0
  119. data/app/helpers/refurl_helper.rb +9 -0
  120. data/app/helpers/xrono/application_helper.rb +54 -0
  121. data/app/mailers/notifier.rb +18 -0
  122. data/app/models/chart_data.rb +20 -0
  123. data/app/models/client.rb +98 -0
  124. data/app/models/comment.rb +8 -0
  125. data/app/models/contact.rb +10 -0
  126. data/app/models/data_vault.rb +6 -0
  127. data/app/models/file_attachment.rb +8 -0
  128. data/app/models/project.rb +76 -0
  129. data/app/models/role.rb +3 -0
  130. data/app/models/roles_user.rb +4 -0
  131. data/app/models/site_settings.rb +4 -0
  132. data/app/models/ticket.rb +85 -0
  133. data/app/models/user.rb +147 -0
  134. data/app/models/weekly_time_report.rb +26 -0
  135. data/app/models/work_unit.rb +99 -0
  136. data/app/models/work_unit/finders.rb +67 -0
  137. data/app/views/admin/_nav.html.haml +1 -0
  138. data/app/views/admin/base/index.html.haml +3 -0
  139. data/app/views/admin/base/reports.html.haml +3 -0
  140. data/app/views/admin/invoices/index.html.haml +19 -0
  141. data/app/views/admin/invoices/show.html.haml +57 -0
  142. data/app/views/admin/payroll/index.html.haml +18 -0
  143. data/app/views/admin/payroll/show.html.haml +56 -0
  144. data/app/views/admin/site_settings/edit.html.haml +32 -0
  145. data/app/views/admin/unentered_time_report/_week_navigation.html.haml +6 -0
  146. data/app/views/admin/unentered_time_report/show.html.haml +16 -0
  147. data/app/views/admin/users/_form.html.haml +48 -0
  148. data/app/views/admin/users/_locked_users.html.haml +31 -0
  149. data/app/views/admin/users/_users_table.html.haml +25 -0
  150. data/app/views/admin/users/edit.html.haml +1 -0
  151. data/app/views/admin/users/index.html.haml +14 -0
  152. data/app/views/admin/users/locked_clients.html.haml +13 -0
  153. data/app/views/admin/users/locked_users.html.haml +13 -0
  154. data/app/views/admin/users/new.html.haml +1 -0
  155. data/app/views/admin/users/projects.html.haml +25 -0
  156. data/app/views/admin/users/unlocked_clients.html.haml +13 -0
  157. data/app/views/admin/weekly_time_report/_week_pagination.html.haml +11 -0
  158. data/app/views/admin/weekly_time_report/show.html.haml +41 -0
  159. data/app/views/client_login/_work_unit_index.haml +16 -0
  160. data/app/views/client_login/base/index.html.haml +18 -0
  161. data/app/views/client_login/clients/show.html.haml +32 -0
  162. data/app/views/client_login/contacts/_form.html.haml +34 -0
  163. data/app/views/client_login/contacts/edit.html.haml +4 -0
  164. data/app/views/client_login/contacts/index.html.haml +30 -0
  165. data/app/views/client_login/contacts/new.html.haml +5 -0
  166. data/app/views/client_login/contacts/show.html.haml +26 -0
  167. data/app/views/client_login/projects/_tickets.html.haml +31 -0
  168. data/app/views/client_login/projects/show.html.haml +8 -0
  169. data/app/views/client_login/reports/index.html.haml +4 -0
  170. data/app/views/client_login/reports/work_units.html.haml +96 -0
  171. data/app/views/client_login/tickets/_form.html.haml +33 -0
  172. data/app/views/client_login/tickets/new.html.haml +1 -0
  173. data/app/views/client_login/tickets/show.html.haml +23 -0
  174. data/app/views/client_login/work_units/show.html.haml +18 -0
  175. data/app/views/clients/_breadcrumbs.html.haml +2 -0
  176. data/app/views/clients/_clients_table.html.haml +24 -0
  177. data/app/views/clients/_form.html.haml +34 -0
  178. data/app/views/clients/_projects.html.haml +31 -0
  179. data/app/views/clients/edit.html.haml +1 -0
  180. data/app/views/clients/inactive_clients.html.haml +9 -0
  181. data/app/views/clients/index.html.haml +8 -0
  182. data/app/views/clients/new.html.haml +1 -0
  183. data/app/views/clients/show.html.haml +34 -0
  184. data/app/views/clients/suspended_clients.html.haml +8 -0
  185. data/app/views/comments/_comment.html.haml +46 -0
  186. data/app/views/comments/_comment_form.haml +29 -0
  187. data/app/views/comments/_comments.html.haml +3 -0
  188. data/app/views/contacts/_form.html.haml +34 -0
  189. data/app/views/contacts/edit.html.haml +1 -0
  190. data/app/views/contacts/index.html.haml +23 -0
  191. data/app/views/contacts/new.html.haml +1 -0
  192. data/app/views/contacts/show.html.haml +26 -0
  193. data/app/views/dashboard/base/_two_week_time_report.html.haml +13 -0
  194. data/app/views/dashboard/base/_week_pagination.html.haml +10 -0
  195. data/app/views/dashboard/base/calendar.html.haml +1 -0
  196. data/app/views/dashboard/base/calendar.js.erb +3 -0
  197. data/app/views/dashboard/base/index.html.haml +18 -0
  198. data/app/views/devise/confirmations/new.html.haml +9 -0
  199. data/app/views/devise/mailer/confirmation_instructions.html.haml +4 -0
  200. data/app/views/devise/mailer/reset_password_instructions.html.haml +6 -0
  201. data/app/views/devise/mailer/unlock_instructions.html.haml +5 -0
  202. data/app/views/devise/passwords/edit.html.haml +17 -0
  203. data/app/views/devise/passwords/new.html.haml +12 -0
  204. data/app/views/devise/registrations/edit.html.haml +36 -0
  205. data/app/views/devise/registrations/new.html.haml +32 -0
  206. data/app/views/devise/sessions/new.html.haml +22 -0
  207. data/app/views/devise/shared/_links.haml +15 -0
  208. data/app/views/devise/unlocks/new.html.haml +9 -0
  209. data/app/views/file_attachments/_file_attachment.html.haml +10 -0
  210. data/app/views/file_attachments/_file_attachment_combined.html.haml +30 -0
  211. data/app/views/file_attachments/_file_attachments.html.haml +5 -0
  212. data/app/views/file_attachments/new.html.haml +17 -0
  213. data/app/views/layouts/application.html.haml +32 -0
  214. data/app/views/layouts/doorkeeper.html.haml +31 -0
  215. data/app/views/notifier/daily.text.erb +8 -0
  216. data/app/views/notifier/work_unit_notification.text.erb +17 -0
  217. data/app/views/projects/_breadcrumbs.html.haml +5 -0
  218. data/app/views/projects/_form.html.haml +94 -0
  219. data/app/views/projects/_pie_chart_metrics.html.haml +4 -0
  220. data/app/views/projects/_specify.html.haml +11 -0
  221. data/app/views/projects/_ticket_table.html.haml +36 -0
  222. data/app/views/projects/edit.html.haml +2 -0
  223. data/app/views/projects/new.html.haml +2 -0
  224. data/app/views/projects/show.html.haml +57 -0
  225. data/app/views/shared/_calendar.html.haml +86 -0
  226. data/app/views/shared/_data_vault.html.haml +16 -0
  227. data/app/views/shared/_files_and_comments.html.haml +21 -0
  228. data/app/views/shared/_flashes.html.haml +10 -0
  229. data/app/views/shared/_javascripts.html.haml +8 -0
  230. data/app/views/shared/_menu.html.haml +14 -0
  231. data/app/views/shared/_user_roles.html.haml +7 -0
  232. data/app/views/shared/_work_unit_index.html.haml +24 -0
  233. data/app/views/tickets/_add_ticket.html.haml +27 -0
  234. data/app/views/tickets/_breadcrumbs.html.haml +8 -0
  235. data/app/views/tickets/_form.html.haml +40 -0
  236. data/app/views/tickets/_ticket_detail.html.haml +18 -0
  237. data/app/views/tickets/edit.html.haml +2 -0
  238. data/app/views/tickets/modal_new.html.haml +28 -0
  239. data/app/views/tickets/new.html.haml +1 -0
  240. data/app/views/tickets/show.html.haml +28 -0
  241. data/app/views/tickets/ticket_detail.html.haml +18 -0
  242. data/app/views/users/_change_password_form.haml +13 -0
  243. data/app/views/users/_user_head.html.haml +7 -0
  244. data/app/views/users/edit.haml +34 -0
  245. data/app/views/users/historical_time.html.haml +5 -0
  246. data/app/views/users/index.html.haml +18 -0
  247. data/app/views/users/show.html.haml +13 -0
  248. data/app/views/work_units/_add_work_unit.html.haml +81 -0
  249. data/app/views/work_units/_breadcrumbs.html.haml +11 -0
  250. data/app/views/work_units/_dashboard_line.html.haml +6 -0
  251. data/app/views/work_units/_edit_work_unit.haml +44 -0
  252. data/app/views/work_units/_shared_form.haml +40 -0
  253. data/app/views/work_units/edit.html.haml +2 -0
  254. data/app/views/work_units/index.html.haml +7 -0
  255. data/app/views/work_units/new.html.haml +6 -0
  256. data/app/views/work_units/show.html.haml +25 -0
  257. data/app/workers/daily_notifier_worker.rb +7 -0
  258. data/app/workers/work_unit_notifier_worker.rb +7 -0
  259. data/config.ru +7 -0
  260. data/config/admin_navigation.rb +71 -0
  261. data/config/admin_reports_navigation.rb +6 -0
  262. data/config/boot.rb +6 -0
  263. data/config/client_navigation.rb +64 -0
  264. data/config/cucumber.yml +8 -0
  265. data/config/initializers/00_requirements.rb +2 -0
  266. data/config/initializers/backtrace_silencers.rb +7 -0
  267. data/config/initializers/devise.rb +144 -0
  268. data/config/initializers/doorkeeper.rb +32 -0
  269. data/config/initializers/doorkeeper_monkeypatch.rb +66 -0
  270. data/config/initializers/github_concern.rb +4 -0
  271. data/config/initializers/haml.rb +13 -0
  272. data/config/initializers/inflections.rb +10 -0
  273. data/config/initializers/load_controller_mixins.rb +1 -0
  274. data/config/initializers/mime_types.rb +5 -0
  275. data/config/initializers/paper_clip.rb +1 -0
  276. data/config/initializers/simple_navigation.rb +1 -0
  277. data/config/initializers/wrap_parameters.rb +14 -0
  278. data/config/locales/devise.en.yml +39 -0
  279. data/config/locales/doorkeeper.en.yml +23 -0
  280. data/config/locales/en.yml +201 -0
  281. data/config/locales/faker.en.yml +48 -0
  282. data/config/navigation.rb +78 -0
  283. data/config/rackspace_cloudfiles.yml +14 -0
  284. data/config/routes.rb +113 -0
  285. data/config/secondary_navigation.rb +65 -0
  286. data/config/trinidad.yml +4 -0
  287. data/config/warble.rb +144 -0
  288. data/db/migrate/20100825165612_create_clients.rb +14 -0
  289. data/db/migrate/20100825185313_create_projects.rb +14 -0
  290. data/db/migrate/20100825223943_create_tickets.rb +16 -0
  291. data/db/migrate/20100902174920_create_work_units.rb +14 -0
  292. data/db/migrate/20100902190129_add_missing_fields_to_work_units.rb +13 -0
  293. data/db/migrate/20100904173839_add_guid_to_clients_projects_tickets_and_work_orders.rb +13 -0
  294. data/db/migrate/20100922181642_create_comments.rb +19 -0
  295. data/db/migrate/20100927180254_devise_create_users.rb +32 -0
  296. data/db/migrate/20100927184505_add_details_to_users.rb +13 -0
  297. data/db/migrate/20100927191329_add_user_to_work_units.rb +9 -0
  298. data/db/migrate/20100928163356_create_roles.rb +22 -0
  299. data/db/migrate/20100928203745_create_addresses.rb +19 -0
  300. data/db/migrate/20100928204544_create_contacts.rb +17 -0
  301. data/db/migrate/20101007175626_add_lockable_to_users.rb +11 -0
  302. data/db/migrate/20101020162216_add_paid_at_to_work_units_table.rb +9 -0
  303. data/db/migrate/20101020162516_add_check_number_to_work_units_table.rb +9 -0
  304. data/db/migrate/20101020215135_add_paid_field_to_work_units_table.rb +9 -0
  305. data/db/migrate/20101020215226_add_invoiced_field_to_work_units_table.rb +9 -0
  306. data/db/migrate/20101020215457_remove_paid_at_from_work_units_table.rb +9 -0
  307. data/db/migrate/20101020215624_remove_check_number_from_work_units_table.rb +9 -0
  308. data/db/migrate/20101025161443_add_invoiced_at_to_work_units_table.rb +9 -0
  309. data/db/migrate/20101026151900_add_initials_to_clients.rb +9 -0
  310. data/db/migrate/20101029145059_create_file_attachments.rb +18 -0
  311. data/db/migrate/20101030142615_add_guid_to_users.rb +9 -0
  312. data/db/migrate/20101102145137_add_paid_at_to_work_units.rb +9 -0
  313. data/db/migrate/20101112151337_add_active_to_comments.rb +9 -0
  314. data/db/migrate/20101119172727_add_recieves_email_to_contact.rb +10 -0
  315. data/db/migrate/20101122225423_projects_users.rb +11 -0
  316. data/db/migrate/20101129214537_remove_projects_users_table.rb +11 -0
  317. data/db/migrate/20101130225328_contact_receives_email.rb +9 -0
  318. data/db/migrate/20101202203051_add_address_to_contacts.rb +19 -0
  319. data/db/migrate/20101202214746_remove_address_table.rb +17 -0
  320. data/db/migrate/20101216214304_add_hours_type_to_work_unit.rb +9 -0
  321. data/db/migrate/20101217152455_add_effective_hours_to_work_units.rb +9 -0
  322. data/db/migrate/20101228154820_add_overtime_multiplier_to_clients_table.rb +9 -0
  323. data/db/migrate/20101228154951_add_overtime_multiplier_to_projects_table.rb +9 -0
  324. data/db/migrate/20101228155122_create_site_settings_table.rb +11 -0
  325. data/db/migrate/20110107190927_add_site_logo_columns_to_site_settings.rb +15 -0
  326. data/db/migrate/20110111155513_add_total_yearly_pto_per_user_to_site_settings.rb +9 -0
  327. data/db/migrate/20110111220015_add_not_valid_to_file_attachment.rb +9 -0
  328. data/db/migrate/20110125153818_add_fullwidth_to_users.rb +9 -0
  329. data/db/migrate/20110222162404_add_daily_target_hours_to_users.rb +9 -0
  330. data/db/migrate/20110310021732_add_client_to_site_settings.rb +9 -0
  331. data/db/migrate/20110630194044_add_expanded_calendar_to_user.rb +9 -0
  332. data/db/migrate/20110922175742_add_state_to_tickets.rb +9 -0
  333. data/db/migrate/20111212193345_add_estimated_hours_to_tickets.rb +5 -0
  334. data/db/migrate/20111216152557_add_client_boolean_to_user.rb +5 -0
  335. data/db/migrate/20111220193307_add_repo_and_branch_to_projects_and_ticket.rb +6 -0
  336. data/db/migrate/20111220193619_create_git_pushes.rb +9 -0
  337. data/db/migrate/20111220193620_create_git_commits.rb +10 -0
  338. data/db/migrate/20111220193621_add_user_id_to_git_push.rb +5 -0
  339. data/db/migrate/20111220193622_create_github_concernable_git_pushes.rb +11 -0
  340. data/db/migrate/20111227140859_add_token_to_user.rb +7 -0
  341. data/db/migrate/20120102185406_add_completed_boolean_to_ticket.rb +5 -0
  342. data/db/migrate/20120104132121_add_completed_to_project.rb +5 -0
  343. data/db/migrate/20120124184915_modify_project_for_new_git_integration.rb +11 -0
  344. data/db/migrate/20120125145833_add_rates_to_project.rb +6 -0
  345. data/db/migrate/20120126151009_create_data_vaults.rb +11 -0
  346. data/db/migrate/20120126151341_create_versions.rb +18 -0
  347. data/db/migrate/20120126154403_rename_column_data_to_encrypted_data.rb +8 -0
  348. data/db/migrate/20120221193022_add_index_to_user_id_on_work_units.rb +5 -0
  349. data/db/migrate/20120221195345_acts_as_taggable_on_migration.rb +30 -0
  350. data/db/migrate/20120320212313_remove_timestamps_from_roles_users_table.rb +11 -0
  351. data/db/migrate/20120710034831_create_doorkeeper_tables.rb +42 -0
  352. data/db/migrate/20121021122002_add_missing_indices.rb +21 -0
  353. data/doc/website/favicon.ico +0 -0
  354. data/doc/website/images/clock_box.png +0 -0
  355. data/doc/website/images/demo.png +0 -0
  356. data/doc/website/images/download.png +0 -0
  357. data/doc/website/images/fork_me.png +0 -0
  358. data/doc/website/images/lightbox-blank.gif +0 -0
  359. data/doc/website/images/lightbox-btn-close.gif +0 -0
  360. data/doc/website/images/lightbox-btn-next.gif +0 -0
  361. data/doc/website/images/lightbox-btn-prev.gif +0 -0
  362. data/doc/website/images/lightbox-ico-loading.gif +0 -0
  363. data/doc/website/images/main_bg.png +0 -0
  364. data/doc/website/images/nav_bg.png +0 -0
  365. data/doc/website/images/screen_1.png +0 -0
  366. data/doc/website/images/screen_2.png +0 -0
  367. data/doc/website/images/screen_3.png +0 -0
  368. data/doc/website/images/screen_4.png +0 -0
  369. data/doc/website/images/screen_5.png +0 -0
  370. data/doc/website/images/screen_6.png +0 -0
  371. data/doc/website/images/support.png +0 -0
  372. data/doc/website/images/thumb_1.png +0 -0
  373. data/doc/website/images/thumb_2.png +0 -0
  374. data/doc/website/images/thumb_3.png +0 -0
  375. data/doc/website/images/thumb_4.png +0 -0
  376. data/doc/website/images/thumb_5.png +0 -0
  377. data/doc/website/images/thumb_6.png +0 -0
  378. data/doc/website/images/xrono_logo.png +0 -0
  379. data/doc/website/index.html +274 -0
  380. data/doc/website/javascripts/application.js +18 -0
  381. data/doc/website/javascripts/greyscalebasic_regular.typeface.js +1 -0
  382. data/doc/website/javascripts/jquery.lightbox-0.5.min.js +42 -0
  383. data/doc/website/javascripts/jquery.min.js +32 -0
  384. data/doc/website/javascripts/jquery.tweet.js +181 -0
  385. data/doc/website/javascripts/typeface.js +873 -0
  386. data/doc/website/javascripts/verdana_regular.typeface.js +1 -0
  387. data/doc/website/stylesheets/jquery.lightbox.css +104 -0
  388. data/doc/website/stylesheets/jquery.tweet.css +38 -0
  389. data/doc/website/stylesheets/reset-fonts-grids.css +32 -0
  390. data/doc/website/stylesheets/screen.css +228 -0
  391. data/doc/xrono_dash.png +0 -0
  392. data/doc/xrono_ticket.png +0 -0
  393. data/features/admin/invoices.feature +37 -0
  394. data/features/admin/manage_users.feature +17 -0
  395. data/features/admin/payroll.feature +64 -0
  396. data/features/admin/roles.feature +9 -0
  397. data/features/admin/site_settings.feature +19 -0
  398. data/features/admin/unentered_time_report.feature +38 -0
  399. data/features/admin/weekly_time_report.feature +14 -0
  400. data/features/api/v1/clients_controller.feature +58 -0
  401. data/features/authorization-admin.feature +34 -0
  402. data/features/authorization-non-admin.feature +38 -0
  403. data/features/client_login.feature +15 -0
  404. data/features/collaborative_work_units.feature +112 -0
  405. data/features/daily_emails.feature +11 -0
  406. data/features/dashboard.feature +74 -0
  407. data/features/manage_clients.feature +116 -0
  408. data/features/manage_comments.feature +30 -0
  409. data/features/manage_contacts.feature +56 -0
  410. data/features/manage_file_attachments.feature +51 -0
  411. data/features/manage_projects.feature +92 -0
  412. data/features/manage_self.feature +48 -0
  413. data/features/manage_tickets.feature +74 -0
  414. data/features/manage_users.feature +76 -0
  415. data/features/manage_work_units.feature +115 -0
  416. data/features/mockups/images/asset-tracker-client-show.png +0 -0
  417. data/features/mockups/images/asset-tracker.png +0 -0
  418. data/features/mockups/images/ixi - asset tracker v3 homepage - no time entered modal.png +0 -0
  419. data/features/mockups/images/ixi - asset tracker v3 homepage - schedule on modal.png +0 -0
  420. data/features/mockups/images/ixi - asset tracker v3 homepage.png +0 -0
  421. data/features/mockups/ixi - asset tracker v3 homepage - no time entered modal.bmml +194 -0
  422. data/features/mockups/ixi - asset tracker v3 homepage - schedule on modal.bmml +208 -0
  423. data/features/mockups/ixi - asset tracker v3 homepage.bmml +172 -0
  424. data/features/project_assignment.feature +69 -0
  425. data/features/search_work_units.feature +38 -0
  426. data/features/step_definitions/calendar_expansion_steps.rb +7 -0
  427. data/features/step_definitions/client_login_steps.rb +8 -0
  428. data/features/step_definitions/client_steps.rb +29 -0
  429. data/features/step_definitions/comment_steps.rb +4 -0
  430. data/features/step_definitions/contact_steps.rb +22 -0
  431. data/features/step_definitions/debug_steps.rb +5 -0
  432. data/features/step_definitions/devise_steps.rb +26 -0
  433. data/features/step_definitions/email_steps.rb +74 -0
  434. data/features/step_definitions/file_attachment_steps.rb +8 -0
  435. data/features/step_definitions/modified_web_steps.rb +25 -0
  436. data/features/step_definitions/pickle_steps.rb +101 -0
  437. data/features/step_definitions/project_steps.rb +32 -0
  438. data/features/step_definitions/search_steps.rb +3 -0
  439. data/features/step_definitions/ticket_board_steps.rb +39 -0
  440. data/features/step_definitions/ticket_detail_steps.rb +9 -0
  441. data/features/step_definitions/ticket_steps.rb +12 -0
  442. data/features/step_definitions/unentered_time_report_steps.rb +3 -0
  443. data/features/step_definitions/user_steps.rb +43 -0
  444. data/features/step_definitions/web_steps.rb +274 -0
  445. data/features/step_definitions/weekly_time_report_steps.rb +7 -0
  446. data/features/step_definitions/work_unit_index_steps.rb +8 -0
  447. data/features/step_definitions/work_unit_steps.rb +39 -0
  448. data/features/support/email.rb +21 -0
  449. data/features/support/env.rb +80 -0
  450. data/features/support/paths.rb +96 -0
  451. data/features/support/pickle.rb +26 -0
  452. data/features/support/selectors.rb +51 -0
  453. data/features/support/xrono.rb +19 -0
  454. data/features/work_unit_notifier.feature +11 -0
  455. data/lib/controller_mixins.rb +5 -0
  456. data/lib/controller_mixins/authorization.rb +15 -0
  457. data/lib/controller_mixins/contacts.rb +37 -0
  458. data/lib/controller_mixins/generic.rb +15 -0
  459. data/lib/controller_mixins/tickets.rb +35 -0
  460. data/lib/controller_mixins/work_units.rb +13 -0
  461. data/lib/date_ext.rb +16 -0
  462. data/lib/guid_referenced.rb +19 -0
  463. data/lib/tasks/.gitkeep +0 -0
  464. data/lib/tasks/acts_as_commentable_tasks.rake +4 -0
  465. data/lib/tasks/cucumber.rake +65 -0
  466. data/lib/tasks/git_repo_tasks.rake +47 -0
  467. data/lib/tasks/notifications.rake +8 -0
  468. data/lib/tasks/paperclip_tasks.rake +79 -0
  469. data/lib/xrono.rb +1 -0
  470. data/lib/xrono/engine.rb +45 -0
  471. data/public/404.html +26 -0
  472. data/public/422.html +26 -0
  473. data/public/500.html +26 -0
  474. data/public/favicon.ico +0 -0
  475. data/public/favicon.psd +0 -0
  476. data/public/robots.txt +5 -0
  477. data/public/stylesheets/.gitkeep +0 -0
  478. data/public/stylesheets/ie.css +53 -0
  479. data/public/stylesheets/print.css +57 -0
  480. data/public/stylesheets/screen.css +182 -0
  481. data/script/cucumber +10 -0
  482. data/script/rails +6 -0
  483. data/spec/blueprints.rb +86 -0
  484. data/spec/controllers/api/v1/clients_controller_spec.rb +41 -0
  485. data/spec/controllers/api/v1/projects_controller_spec.rb +54 -0
  486. data/spec/controllers/api/v1/tickets_controller_spec.rb +122 -0
  487. data/spec/controllers/api/v1/tokens_controller_spec.rb +52 -0
  488. data/spec/controllers/client_login/clients_controller_spec.rb +5 -0
  489. data/spec/controllers/client_login/reports_controller_spec.rb +5 -0
  490. data/spec/controllers/data_vaults_controller_spec.rb +5 -0
  491. data/spec/internal/app/controllers/application_controller.rb +5 -0
  492. data/spec/internal/config/admin_navigation.rb +71 -0
  493. data/spec/internal/config/database.yml +14 -0
  494. data/spec/internal/config/database.yml.example +20 -0
  495. data/spec/internal/config/database.yml.mysql +20 -0
  496. data/spec/internal/config/database.yml.mysql.jruby +20 -0
  497. data/spec/internal/config/database.yml.postgres +20 -0
  498. data/spec/internal/config/database.yml.postgres.jruby +20 -0
  499. data/spec/internal/config/navigation.rb +80 -0
  500. data/spec/internal/config/routes.rb +3 -0
  501. data/spec/internal/config/secondary_navigation.rb +65 -0
  502. data/spec/internal/db/schema.rb +296 -0
  503. data/spec/internal/log/.gitignore +1 -0
  504. data/spec/internal/public/favicon.ico +0 -0
  505. data/spec/internal/public/system/attachment_files/2/original/tmp.txt +1 -0
  506. data/spec/internal/public/system/attachment_files/3/original/tmp.txt +1 -0
  507. data/spec/lib/date_ext_spec.rb +40 -0
  508. data/spec/mailers/notifier_spec.rb +9 -0
  509. data/spec/models/client_spec.rb +167 -0
  510. data/spec/models/contact_spec.rb +29 -0
  511. data/spec/models/data_vault_spec.rb +5 -0
  512. data/spec/models/project_spec.rb +115 -0
  513. data/spec/models/ticket_spec.rb +223 -0
  514. data/spec/models/user_spec.rb +258 -0
  515. data/spec/models/work_unit_spec.rb +467 -0
  516. data/spec/spec_helper.rb +44 -0
  517. data/spec/support/xrono/macros.rb +15 -0
  518. data/vendor/plugins/.gitkeep +0 -0
  519. data/xrono.gemspec +50 -0
  520. metadata +1209 -0
@@ -0,0 +1,10 @@
1
+ class Contact < ActiveRecord::Base
2
+ belongs_to :client
3
+
4
+ validates_presence_of :first_name
5
+ validates_presence_of :last_name
6
+ validates_presence_of :email_address
7
+
8
+ scope :for_client, lambda{ |client| where(:client_id => client.id)}
9
+ scope :receives_email, where(:receives_email => true)
10
+ end
@@ -0,0 +1,6 @@
1
+ class DataVault < ActiveRecord::Base
2
+ has_paper_trail
3
+ attr_encrypted :data, :key => "SECRET!", :attribute => :encrypted_data
4
+
5
+ belongs_to :data_vaultable, :polymorphic => true
6
+ end
@@ -0,0 +1,8 @@
1
+ class FileAttachment < ActiveRecord::Base
2
+ belongs_to :project
3
+ belongs_to :client
4
+ belongs_to :ticket
5
+
6
+ has_attached_file :attachment_file
7
+ validates_attachment_presence :attachment_file
8
+ end
@@ -0,0 +1,76 @@
1
+ class Project < ActiveRecord::Base
2
+ include GuidReferenced
3
+ acts_as_commentable
4
+ acts_as_authorization_object
5
+ acts_as_taggable
6
+
7
+ github_concern :repo => :git_repo_name
8
+
9
+ belongs_to :client
10
+ has_many :tickets
11
+ has_many :comments, :as => :commentable
12
+ has_many :file_attachments
13
+ has_one :data_vault, :as => :data_vaultable
14
+
15
+ validates_presence_of :name
16
+ validates_presence_of :client_id
17
+ validates_uniqueness_of :name, :scope => :client_id
18
+
19
+ after_create :create_data_vault
20
+
21
+ scope :sort_by_name, order('name ASC')
22
+ scope :for_client, lambda {|client| where :client_id => client.id }
23
+ scope :for_client_id, lambda {|client_id| where :client_id => client_id }
24
+ scope :incomplete, where(:completed => false)
25
+ scope :complete, where(:completed => true)
26
+ scope :with_git_repos, where(arel_table[:git_repo_url].not_eq(nil))
27
+
28
+ scope :for_user, lambda{|user|
29
+ joins("INNER JOIN roles r ON r.authorizable_type='#{model_name}' AND r.authorizable_id=projects.id").joins("INNER JOIN roles_users ru ON ru.role_id = r.id").joins("INNER JOIN users u ON ru.user_id = u.id").where("ru.user_id = #{user.id}")
30
+ }
31
+
32
+ scope :for_user_and_role, lambda{|user, role|
33
+ joins("INNER JOIN roles r ON r.authorizable_type='#{model_name}' AND r.authorizable_id=projects.id").joins("INNER JOIN roles_users ru ON ru.role_id = r.id").joins("INNER JOIN users u ON ru.user_id = u.id").where("ru.user_id = #{user.id} AND r.name = '#{role}'")
34
+ }
35
+
36
+ def uninvoiced_hours
37
+ WorkUnit.for_project(self).not_invoiced.sum(:effective_hours)
38
+ end
39
+
40
+ def hours
41
+ WorkUnit.for_project(self).sum(:effective_hours)
42
+ end
43
+
44
+ def hours_this_month
45
+ WorkUnit.for_project(self).scheduled_between(Time.zone.now.beginning_of_month, Time.zone.now.end_of_month).sum(:effective_hours)
46
+ end
47
+
48
+ def hours_this_week
49
+ WorkUnit.for_project(self).scheduled_between(Time.zone.now.beginning_of_week, Time.zone.now.end_of_week).sum(:effective_hours)
50
+ end
51
+
52
+ def work_units
53
+ WorkUnit.for_project(self)
54
+ end
55
+
56
+ def to_s
57
+ if new_record?
58
+ I18n.t(:new_project)
59
+ else
60
+ name
61
+ end
62
+ end
63
+
64
+ def allows_access?(user)
65
+ accepts_roles_by?(user) || user.admin?
66
+ end
67
+
68
+ def files_and_comments
69
+ [comments, file_attachments].flatten.sort_by {|x| x.created_at}
70
+ end
71
+
72
+ private
73
+ def create_data_vault
74
+ self.data_vault = DataVault.create(:data_vaultable => self)
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ class Role < ActiveRecord::Base
2
+ acts_as_authorization_role :join_table_name => :roles_users
3
+ end
@@ -0,0 +1,4 @@
1
+ class RolesUser < ActiveRecord::Base
2
+ belongs_to :user
3
+ belongs_to :role
4
+ end
@@ -0,0 +1,4 @@
1
+ class SiteSettings < ActiveRecord::Base
2
+ has_attached_file :site_logo, :default_url => '/assets/logo_97x40.png'
3
+ belongs_to :client
4
+ end
@@ -0,0 +1,85 @@
1
+ class Ticket < ActiveRecord::Base
2
+ include GuidReferenced
3
+ acts_as_commentable
4
+ belongs_to :project
5
+ has_many :work_units
6
+ has_many :file_attachments
7
+
8
+ validates_presence_of :project_id
9
+ validates_presence_of :name
10
+
11
+ scope :for_client, lambda{|client| joins({:project => [:client]}).where("clients.id = ?", client.id) }
12
+ scope :for_project, lambda{|project| where(:project_id => project.id) }
13
+ scope :for_projects, lambda{|projects| where(:project_id => projects.map(&:id)) }
14
+ scope :for_project_id, lambda{|project_id| where :project_id => project_id }
15
+ scope :sort_by_name, order('name ASC')
16
+ scope :for_state, lambda { |state| where(:state => state) }
17
+ scope :incomplete, :conditions => ["completed = ?", false]
18
+ scope :complete, :conditions => ["completed = ?", true]
19
+
20
+ scope :for_user, lambda{|user|
21
+ joins("INNER JOIN projects p ON p.id=tickets.project_id").joins("INNER JOIN roles r ON r.authorizable_type='Project' AND r.authorizable_id=p.id").joins("INNER JOIN roles_users ru ON ru.role_id = r.id").where("ru.user_id = ?", user.id)
22
+ }
23
+
24
+ scope :for_user_and_role, lambda{|user, role|
25
+ joins("INNER JOIN projects p ON p.id=tickets.project_id").joins("INNER JOIN roles r ON r.authorizable_type='Project' AND r.authorizable_id=p.id").joins("INNER JOIN roles_users ru ON ru.role_id = r.id").where("ru.user_id = #{user.id} AND r.name = '#{role}'")
26
+ }
27
+
28
+ scope :for_repo_and_branch, lambda{|repo,branch|
29
+ joins("INNER JOIN projects p ON p.id=tickets.project_id").where("p.git_repo_name = '#{repo}' and tickets.git_branch = '#{branch}'")
30
+ }
31
+
32
+ scope :for_repo_url_and_branch, lambda{|repo,branch|
33
+ joins("INNER JOIN projects p ON p.id=tickets.project_id").where("p.git_repo_url = '#{repo}' and tickets.git_branch = '#{branch}'")
34
+ }
35
+
36
+ def client
37
+ project.client
38
+ end
39
+
40
+ def to_s
41
+ name
42
+ end
43
+
44
+ def hours
45
+ work_units.sum(:effective_hours)
46
+ end
47
+
48
+ def unpaid_hours
49
+ work_units.unpaid.sum(:effective_hours)
50
+ end
51
+
52
+ def uninvoiced_hours
53
+ work_units.not_invoiced.sum(:effective_hours)
54
+ end
55
+
56
+ def percentage_complete
57
+ return 100 unless self.estimated_hours
58
+ begin
59
+ percent = (((self.hours / self.estimated_hours)).to_f * 100.00).round(2)
60
+ [percent, 100].min
61
+ rescue
62
+ 0
63
+ end
64
+ end
65
+
66
+ def remaining_hours
67
+ if self.estimated_hours and self.estimated_hours > self.hours
68
+ self.estimated_hours - self.hours
69
+ else
70
+ 0
71
+ end
72
+ end
73
+
74
+ def long_name
75
+ "Ticket: [#{id}] - #{project.name} - #{name} ticket for #{client.name}"
76
+ end
77
+
78
+ def allows_access?(user)
79
+ project.accepts_roles_by?(user) || user.admin?
80
+ end
81
+
82
+ def files_and_comments
83
+ [comments, file_attachments].flatten.sort_by {|x| x.created_at}
84
+ end
85
+ end
@@ -0,0 +1,147 @@
1
+ class User < ActiveRecord::Base
2
+ # Include default devise modules. Others available are:
3
+ # :token_authenticatable, :confirmable, :lockable and :timeoutable
4
+ devise :database_authenticatable, :rememberable, :trackable, :validatable, :lockable, :token_authenticatable
5
+
6
+ include Gravtastic
7
+ gravtastic
8
+ is_gravtastic!
9
+
10
+ acts_as_authorization_subject :association_name => :roles, :join_table_name => :roles_users
11
+ acts_as_tagger
12
+
13
+ # Setup accessible (or protected) attributes for your model
14
+ attr_accessible :email, :password, :password_confirmation, :remember_me,
15
+ :first_name, :last_name, :middle_initial, :full_width,
16
+ :daily_target_hours, :expanded_calendar, :client
17
+
18
+ validates_presence_of :first_name, :last_name
19
+ validates_length_of :middle_initial, :is => 1
20
+
21
+ has_many :work_units
22
+ has_many :comments
23
+
24
+ # Scopes
25
+ def self.with_unpaid_work_units
26
+ select('distinct users.*').joins(:work_units).where(:work_units => {:paid => [nil, '']})
27
+ end
28
+
29
+ scope :unlocked, where(:locked_at => nil)
30
+ scope :locked, where('locked_at IS NOT NULL')
31
+ scope :sort_by_name, order('first_name ASC')
32
+ scope :developers, lambda { joins("INNER JOIN roles r on r.authorizable_type='Project'").joins("INNER JOIN roles_users ru ON ru.role_id = r.id").joins("INNER JOIN users u ON ru.user_id = u.id").where("r.name = 'developer'").where("ru.user_id = users.id") }
33
+ scope :for_project, lambda{|project|
34
+ joins("INNER JOIN roles r ON r.authorizable_type='Project' AND r.authorizable_id=#{project.id}").joins("INNER JOIN roles_users ru ON ru.role_id = r.id").joins("INNER JOIN users u ON ru.user_id = u.id").where("ru.user_id = users.id")
35
+ }
36
+
37
+ # Return the initials of the User
38
+ def initials
39
+ "#{first_name[0,1]}#{middle_initial}#{last_name[0,1]}".upcase
40
+ end
41
+
42
+ def work_units_between(start_time, end_time)
43
+ work_units.scheduled_between(start_time.beginning_of_day, end_time.end_of_day)
44
+ end
45
+
46
+ def work_units_for_day(time)
47
+ work_units.scheduled_between(time.beginning_of_day, time.end_of_day)
48
+ end
49
+
50
+ def clients_for_day(time)
51
+ work_units_for_day(time).map{|x| x.client}.uniq
52
+ end
53
+
54
+ def work_units_for_week(time)
55
+ work_units.scheduled_between(time.beginning_of_week, time.end_of_week)
56
+ end
57
+
58
+ def hours_entered_for_day(time)
59
+ hours = 0
60
+ work_units_for_day(time).each do |w|
61
+ hours += w.hours
62
+ end
63
+ hours
64
+ end
65
+ def unpaid_work_units
66
+ work_units.unpaid
67
+ end
68
+
69
+ def to_s
70
+ "#{first_name.capitalize} #{middle_initial.capitalize} #{last_name.capitalize}"
71
+ end
72
+
73
+ def client?
74
+ @client ||= has_role?(:client)
75
+ end
76
+
77
+ def developer?
78
+ @developer ||= has_role?(:developer)
79
+ end
80
+
81
+ def admin?
82
+ @admin ||= has_role?(:admin)
83
+ end
84
+
85
+ def locked
86
+ locked_at?
87
+ end
88
+
89
+ def pto_hours_left(date)
90
+ raise "Date must be a date object" unless date.is_a?(Date)
91
+ time = date.to_time_in_current_zone
92
+ SiteSettings.first.total_yearly_pto_per_user - work_units.pto.scheduled_between(time.beginning_of_year, time).sum(:hours)
93
+ end
94
+
95
+ # TODO: refactor this mess
96
+ def expected_hours(date)
97
+ raise "Date must be a date object" unless date.is_a?(Date)
98
+ # no expected hours if the user has never worked
99
+ return 0 unless work_units.present?
100
+ # set the user's first day by the first work unit
101
+ first_day = work_units.first.scheduled_at.to_date
102
+ # no expected hours if their first day is in the future
103
+ return 0 if first_day > date
104
+ if first_day.year == date.year
105
+ # if their first day was in the same year as the date
106
+ # calculate the offset of the first week, incase they dont start on monday
107
+ first_week_offset = first_day.cwday - 1
108
+ # caculate total days from previous week, subtracting the first week offset
109
+ days_from_prev_weeks = ((date.cweek - first_day.cweek) * 5) - first_week_offset
110
+ # get the number of work days in the targeted week that have passed
111
+ days_from_cur_week = [date.cwday, 5].min
112
+ else
113
+ # user has been here all year, count total work days in year
114
+ days_from_prev_weeks = (date.cweek - 1) * 5
115
+ days_from_cur_week = [date.cwday, 5].min
116
+ end
117
+ # calculate expected hours off of total expected days
118
+ (days_from_prev_weeks + days_from_cur_week) * daily_target_hours
119
+ end
120
+
121
+ def target_hours_offset(date)
122
+ raise "Date must be a date object" unless date.is_a?(Date)
123
+ worked_hours = WorkUnit.for_user(self).scheduled_between(date.beginning_of_year, date.end_of_day).sum(:hours)
124
+ worked_hours - expected_hours(date)
125
+ end
126
+
127
+ def entered_time_yesterday?
128
+ return true if !developer? || admin?
129
+ previous_work_days_work_units.any?
130
+ end
131
+
132
+ def previous_work_days_work_units
133
+ work_units_between(Date.current.prev_working_day.beginning_of_day, Date.current.prev_working_day.end_of_day)
134
+ end
135
+
136
+ def percentage_work_for(client, start_date, end_date)
137
+ raise "client must be a Client instance" unless client.is_a?(Client)
138
+ raise "Date must be a date object" unless start_date.is_a?(Date) && end_date.is_a?(Date)
139
+ # note: I'm returning an integer here, as I think that is specific enough for the
140
+ # reporting use case, this can easily be changed if future needs demand
141
+ wu = work_units.scheduled_between(start_date.beginning_of_day, end_date.end_of_day)
142
+ total_hours = wu.sum(:effective_hours)
143
+ client_hours = wu.for_client(client).sum(:effective_hours)
144
+ # Dividing by 0 is bad, mkay?
145
+ total_hours == 0 ? 0 : ((100 * client_hours)/total_hours).round
146
+ end
147
+ end
@@ -0,0 +1,26 @@
1
+ # Get internal/external hour split for a given week
2
+ class WeeklyTimeReport
3
+ def initialize(date_in_week=Time.zone.now, partial_week=false)
4
+ @date = date_in_week
5
+ @partial_week = partial_week
6
+ end
7
+
8
+ def run
9
+ get_weekly_hours(@date)
10
+ end
11
+
12
+ def sum_hours(method, hours)
13
+ hours.select{|wu| wu.send(method) }.sum(&:hours)
14
+ end
15
+
16
+ def get_weekly_hours(date)
17
+ start_date = date.beginning_of_week.to_date
18
+ if @partial_week
19
+ end_date = date
20
+ else
21
+ end_date = date.end_of_week.to_date
22
+ end
23
+ work_units = WorkUnit.scheduled_between(start_date, end_date)
24
+ { external: sum_hours(:external?, work_units).to_i, internal: sum_hours(:internal?, work_units).to_i }
25
+ end
26
+ end
@@ -0,0 +1,99 @@
1
+ class WorkUnit < ActiveRecord::Base
2
+ include GuidReferenced
3
+ extend WorkUnit::Finders
4
+ acts_as_commentable
5
+
6
+ has_many :comments, :as => :commentable
7
+ belongs_to :ticket
8
+ belongs_to :user
9
+
10
+ validates_presence_of :ticket_id, :user_id, :description, :hours, :scheduled_at, :effective_hours, :hours_type
11
+ validates_numericality_of :hours, :greater_than => -1
12
+ before_validation :set_effective_hours!
13
+ after_validation :validate_client_status
14
+ after_create :send_email!
15
+
16
+ def validate_client_status
17
+ if client && client.status == "Inactive"
18
+ self.errors.add(:base, "Cannot create work units on inactive clients.")
19
+ end
20
+ end
21
+
22
+ def internal?
23
+ SiteSettings.first.try(:client) == client
24
+ end
25
+
26
+ def external?
27
+ !internal?
28
+ end
29
+
30
+ def send_email!
31
+ WorkUnitNotifierWorker.perform_async(id, email_list) if email_list.any?
32
+ end
33
+
34
+ def email_list
35
+ Contact.for_client(self.client).receives_email.map(&:email_address)
36
+ end
37
+
38
+ def client
39
+ (ticket && ticket.client) ? ticket.project.client : nil
40
+ end
41
+
42
+ def project
43
+ ticket.project
44
+ end
45
+
46
+ def unpaid?
47
+ paid.blank?
48
+ end
49
+
50
+ def paid?
51
+ !unpaid?
52
+ end
53
+
54
+ def invoiced?
55
+ !not_invoiced?
56
+ end
57
+
58
+ def not_invoiced?
59
+ invoiced.blank?
60
+ end
61
+
62
+ def to_s
63
+ if new_record?
64
+ I18n.t(:new_work_unit)
65
+ else
66
+ description[0..80]
67
+ end
68
+ end
69
+
70
+ def allows_access?(user)
71
+ project.accepts_roles_by?(user) || user.has_role?(:admin)
72
+ end
73
+
74
+ def overtime_multiplier
75
+ if ticket && project && project.overtime_multiplier
76
+ project.overtime_multiplier
77
+ elsif client && client.overtime_multiplier
78
+ client.overtime_multiplier
79
+ elsif SiteSettings.first && SiteSettings.first.overtime_multiplier
80
+ SiteSettings.first.overtime_multiplier
81
+ else
82
+ BigDecimal.new("1.5")
83
+ end
84
+ end
85
+
86
+ def set_effective_hours!
87
+ if hours
88
+ if hours_type == "Overtime"
89
+ self.effective_hours = hours * overtime_multiplier
90
+ else
91
+ self.effective_hours = hours
92
+ end
93
+ end
94
+ end
95
+
96
+ def overtime?
97
+ hours_type == "Overtime"
98
+ end
99
+ end