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,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Contact do
4
+ let(:contact) { Contact.make }
5
+ let(:contact_2) { Contact.make }
6
+ let(:contact_2_receives_email) { Contact.make(:receives_email => true) }
7
+ let(:contact_3) { Contact.make }
8
+
9
+ it { should belong_to :client }
10
+ it { should validate_presence_of :first_name }
11
+ it { should validate_presence_of :last_name }
12
+ it { should validate_presence_of :email_address }
13
+
14
+ describe 'for_client' do
15
+ it 'should return the proper list of contacts for the client' do
16
+ client = contact_2.client
17
+ Contact.for_client(client).include?(contact_2).should be_true
18
+ Contact.for_client(client).include?(contact_3).should be_false
19
+ end
20
+ end
21
+
22
+ describe 'receives_email' do
23
+ it 'should return the proper list of contacts that can receive email' do
24
+ client = contact_2_receives_email.client
25
+ Contact.for_client(client).receives_email.include?(contact_2_receives_email).should be_true
26
+ Contact.for_client(client).receives_email.include?(contact_3).should_not be_true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataVault do
4
+ it { should belong_to :data_vaultable }
5
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe Project do
4
+ let(:project) { Project.make(:name => 'New Project') }
5
+ let(:ticket_1) { Ticket.make(:project => project) }
6
+ let(:ticket_2) { Ticket.make(:project => project) }
7
+
8
+ it { should belong_to :client }
9
+ it { should have_many :tickets }
10
+ it { should have_many :comments }
11
+ it { should have_many :file_attachments }
12
+
13
+ it { should validate_presence_of :name }
14
+ it { should validate_presence_of :client_id }
15
+ it { project; should validate_uniqueness_of(:name).scoped_to(:client_id) }
16
+
17
+ describe '#to_s' do
18
+ subject { project.to_s }
19
+
20
+ it 'returns the name of the project as a string' do
21
+ should == 'New Project'
22
+ end
23
+ end
24
+
25
+ describe '#hours' do
26
+ it 'returns total number of hours from all tickets on the project' do
27
+ WorkUnit.make(:ticket => ticket_1)
28
+ WorkUnit.make(:ticket => ticket_2)
29
+ project.hours.should == ticket_1.work_units.first.effective_hours + ticket_2.work_units.first.effective_hours
30
+ end
31
+ end
32
+
33
+ describe '#uninvoiced_hours' do
34
+ it "returns the sum of hours on all the client's work units" do
35
+ work_unit_1 = WorkUnit.make
36
+ ticket = work_unit_1.ticket
37
+ project = work_unit_1.project
38
+ work_unit_2 = WorkUnit.make(:ticket => ticket)
39
+ work_unit_3 = WorkUnit.make(:ticket => ticket, :invoiced => 'Invoiced', :invoiced_at => Time.current)
40
+ total_hours = work_unit_1.effective_hours + work_unit_2.effective_hours
41
+
42
+ project.uninvoiced_hours.should == total_hours
43
+ end
44
+ end
45
+
46
+ describe '.for_user' do
47
+ context 'when a user is assigned to a project' do
48
+ it 'returns the projects to which the user is assigned' do
49
+ user = User.make
50
+ project1 = Project.make
51
+ project2 = Project.make
52
+ user.has_no_roles_for!(project2)
53
+ user.has_role!(:developer, project1)
54
+ Project.for_user(user).include?(project1).should be_true
55
+ Project.for_user(user).include?(project2).should be_false
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#work_units' do
61
+ it 'returns the children work units for that project' do
62
+ work_unit = WorkUnit.make
63
+ project = work_unit.ticket.project
64
+ project.work_units.should == [work_unit]
65
+ end
66
+ end
67
+
68
+ describe "#allows_access?" do
69
+ let(:project) { Project.make }
70
+ let(:user_admin) {
71
+ _user = User.make
72
+ _user.roles.create(:name => 'admin')
73
+ _user
74
+ }
75
+ let(:user_non_admin) { User.make }
76
+ let(:user_with_role) {
77
+ _user = User.make
78
+ _user.roles.create(:name => "client", :authorizable => project)
79
+ _user
80
+ }
81
+
82
+ it "returns true if you have access" do
83
+ project.allows_access?(user_admin).should be_true
84
+ project.allows_access?(user_non_admin).should be_false
85
+ project.allows_access?(user_with_role).should be_true
86
+ end
87
+ end
88
+
89
+ describe "Project#for_user_and_role" do
90
+ it "returns all projects with user and role" do
91
+ project = Project.make
92
+ user = User.make
93
+ user.roles.create(:name => "client", :authorizable => project)
94
+
95
+ Project.for_user_and_role(user, "client").should == [project]
96
+ end
97
+ end
98
+
99
+ describe '#files_and_comments' do
100
+ it 'lists all files and comments' do
101
+ project = Project.make
102
+ comment = project.comments.create(:title => "test", :comment => "test")
103
+ File.open("tmp/tmp.txt", "w") {|f| f.write "test"}
104
+ fa = project.file_attachments.create(:attachment_file => File.open("tmp/tmp.txt","r"))
105
+ project.files_and_comments.should == [comment,fa]
106
+ File.delete("tmp/tmp.txt")
107
+ end
108
+ end
109
+
110
+ describe 'tagging' do
111
+ it 'is taggable' do
112
+ Project.new.is_taggable?.should be_true
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,223 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ticket do
4
+ let(:ticket) { Ticket.make(:name => 'New Ticket') }
5
+
6
+ it { should belong_to :project }
7
+ it { should have_many :work_units }
8
+ it { should have_many :file_attachments }
9
+
10
+ it { should validate_presence_of :project_id }
11
+ it { should validate_presence_of :name }
12
+
13
+ describe '#to_s' do
14
+ subject { ticket.to_s }
15
+
16
+ it 'returns the name of the ticket as a string' do
17
+ should == 'New Ticket'
18
+ end
19
+ end
20
+
21
+ describe '#client' do
22
+ context 'when a ticket exists' do
23
+ it 'returns the client' do
24
+ ticket.client.should == ticket.project.client
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '#unpaid_hours' do
30
+ context 'when there are unpaid work units on a ticket' do
31
+ it 'totals the unpaid hours for that ticket' do
32
+ work_unit_1 = WorkUnit.make(:ticket => ticket)
33
+ work_unit_2 = WorkUnit.make(:ticket => ticket)
34
+ work_unit_3 = WorkUnit.make(:ticket => ticket, :paid => 'paid on 2010-10-25')
35
+ unpaid_hours = work_unit_1.effective_hours + work_unit_2.effective_hours
36
+
37
+ ticket.unpaid_hours.should == unpaid_hours
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#uninvoiced_hours' do
43
+ context 'when there are uninvoiced work units on a ticket' do
44
+ it 'returns the total number of uninvoiced work units for that ticket' do
45
+ ticket = Ticket.make
46
+ work_unit_1 = WorkUnit.make(:ticket => ticket)
47
+ work_unit_2 = WorkUnit.make(:ticket => ticket)
48
+ work_unit_3 = WorkUnit.make(:ticket => ticket, :invoiced => 'invoiced on 2010-10-25')
49
+ uninvoiced_hours = work_unit_1.effective_hours + work_unit_2.effective_hours
50
+
51
+ ticket.uninvoiced_hours.should == uninvoiced_hours
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#long_name' do
57
+ it 'returns a descriptive string with the ticket id, project, and client' do
58
+ long_name = "Ticket: [#{ticket.id}] - #{ticket.project.name} - #{ticket.name} ticket for #{ticket.project.client.name}"
59
+
60
+ ticket.long_name.should == long_name
61
+ end
62
+ end
63
+
64
+ describe '#allows_access?' do
65
+ let(:project) { Project.make }
66
+ let(:ticket) { Ticket.make(:project => project) }
67
+ let(:user) { User.make }
68
+
69
+ it 'returns false if the user does not have access to the parent project' do
70
+ ticket.allows_access?(user).should be_false
71
+ end
72
+
73
+ it 'returns true if the user has access to the parent project' do
74
+ user.has_role!(:developer, project)
75
+ ticket.allows_access?(user).should be_true
76
+ end
77
+ end
78
+
79
+ describe '.for_user_role' do
80
+ context 'when a user has access to a project' do
81
+ let(:project_1) { Project.make }
82
+ let(:project_2) { Project.make }
83
+ let(:user) do
84
+ _user = User.make
85
+ _user.has_role!(:developer, project_1)
86
+ _user.has_role!(:client, project_2)
87
+ _user
88
+ end
89
+ let!(:ticket_1) { Ticket.make(:project => project_1) }
90
+ let!(:ticket_2) { Ticket.make(:project => project_1) }
91
+ let!(:ticket_3) { Ticket.make(:project => project_2) }
92
+
93
+ it 'returns a collection of tickets for all the projects to which the user is assigned' do
94
+ Ticket.for_user_and_role(user, "developer").include?(ticket_1).should be_true
95
+ Ticket.for_user_and_role(user, "developer").include?(ticket_2).should be_true
96
+ Ticket.for_user_and_role(user, "developer").include?(ticket_3).should be_false
97
+ end
98
+ end
99
+ end
100
+
101
+ describe '.for_user' do
102
+ context 'when a user has access to a project' do
103
+ let(:project_1) { Project.make }
104
+ let(:project_2) { Project.make }
105
+ let(:user) do
106
+ _user = User.make
107
+ _user.has_role!(:developer, project_1)
108
+ _user
109
+ end
110
+ let!(:ticket_1) { Ticket.make(:project => project_1) }
111
+ let!(:ticket_2) { Ticket.make(:project => project_1) }
112
+ let!(:ticket_3) { Ticket.make(:project => project_2) }
113
+
114
+ it 'returns a collection of tickets for all the projects to which the user is assigned' do
115
+ Ticket.for_user(user).include?(ticket_1).should be_true
116
+ Ticket.for_user(user).include?(ticket_2).should be_true
117
+ Ticket.for_user(user).include?(ticket_3).should be_false
118
+ end
119
+ end
120
+ end
121
+
122
+ describe 'returns a list of tickets using' do
123
+ let(:project) { Project.make }
124
+
125
+ let!(:ticket) do
126
+ _ticket = project.tickets.make
127
+ _ticket.update_attributes(git_branch: 'feature/test')
128
+ _ticket
129
+ end
130
+
131
+ it ".for_repo_and_branch" do
132
+ project.update_attributes(git_repo_name: 'test')
133
+ Ticket.for_repo_and_branch("test", "feature/test").should include(ticket)
134
+ end
135
+
136
+ it ".for_repo_url_and_branch" do
137
+ project.update_attributes(git_repo_url: 'test')
138
+ Ticket.for_repo_url_and_branch("test", "feature/test").should include(ticket)
139
+ end
140
+ end
141
+
142
+ describe '' do
143
+ let(:ticket) do
144
+ _ticket = Ticket.make
145
+ _ticket.update_attributes(estimated_hours: 10)
146
+ _ticket
147
+ end
148
+
149
+ let(:work_unit_is_under) do
150
+ wu = ticket.work_units.make
151
+ wu.update_column(:effective_hours, 5)
152
+ wu
153
+ end
154
+
155
+ describe '#remaining_hours' do
156
+ let(:work_unit_is_over) do
157
+ wu = ticket.work_units.make
158
+ wu.update_column(:effective_hours, 11)
159
+ wu
160
+ end
161
+
162
+ it 'returns the remaining hours when there are remaining hours' do
163
+ work_unit_is_under
164
+
165
+ ticket.remaining_hours.should == 5
166
+ end
167
+
168
+ it 'returns 0 when there are more effective hours than estimated hours' do
169
+ work_unit_is_over
170
+
171
+ ticket.remaining_hours.should == 0
172
+ end
173
+ end
174
+
175
+ describe '#percentage_complete' do
176
+ let(:ticket_invalid) { Ticket.make }
177
+
178
+ it 'calculates the percentage left on the ticket' do
179
+ work_unit_is_under
180
+
181
+ ticket.percentage_complete.should == 50.0
182
+ end
183
+
184
+ it 'returns 0 when there is an error' do
185
+ ticket_invalid.percentage_complete.should == 0
186
+ end
187
+ end
188
+ end
189
+
190
+ describe '#hours' do
191
+ context 'when there are normal work units with hours' do
192
+ let(:ticket_2) { Ticket.make }
193
+
194
+ it 'returns the correct sum of hours for those work units' do
195
+ work_unit_1 = WorkUnit.make(:ticket => ticket, :hours => '1.0', :hours_type => 'Normal')
196
+ work_unit_2 = WorkUnit.make(:ticket => ticket, :hours => '1.0', :hours_type => 'Normal')
197
+ work_unit_3 = WorkUnit.make(:ticket => ticket_2, :hours => '1.0', :hours_type => 'Normal')
198
+
199
+ ticket.hours.should == 2.0
200
+ end
201
+ end
202
+ end
203
+
204
+ describe 'while being created' do
205
+ it 'creates a new ticket from the blueprint' do
206
+ lambda do
207
+ Ticket.make
208
+ end.should change(Ticket, :count).by(1)
209
+ end
210
+ end
211
+
212
+ describe '#files_and_comments' do
213
+ it 'lists all files and comments' do
214
+ ticket = Ticket.make
215
+ comment = ticket.comments.create(:title => "test", :comment => "test")
216
+ File.open("tmp/tmp.txt", "w") {|f| f.write "test"}
217
+ fa = ticket.file_attachments.create(:attachment_file => File.open("tmp/tmp.txt","r"))
218
+ ticket.files_and_comments.should == [comment,fa]
219
+ File.delete("tmp/tmp.txt")
220
+ end
221
+ end
222
+
223
+ end
@@ -0,0 +1,258 @@
1
+ require 'spec_helper'
2
+
3
+ describe User do
4
+ let(:user) { User.make }
5
+ let(:user_2) { User.make }
6
+ let(:work_unit1) { WorkUnit.make(:user => user) }
7
+ let(:work_unit2) { WorkUnit.make(:user => user) }
8
+ let(:work_unit3) { WorkUnit.make(:user => user) }
9
+
10
+ it { should validate_presence_of :first_name }
11
+ it { should validate_presence_of :last_name }
12
+ describe '.with_unpaid_work_units' do
13
+ subject { User.with_unpaid_work_units }
14
+
15
+ before do
16
+ work_unit1.update_attributes(:paid => nil, :paid_at => nil)
17
+ work_unit2.update_attributes(:paid => nil, :paid_at => nil)
18
+ end
19
+
20
+ it 'returns a collection of users who have unpaid work units' do
21
+ should == [user]
22
+ end
23
+ end
24
+
25
+ describe '.unlocked' do
26
+ subject { User.unlocked }
27
+
28
+ before { user.unlock_access! }
29
+
30
+ it 'returns a collection of users who are not locked' do
31
+ should == [user]
32
+ end
33
+ end
34
+
35
+ describe '.sort_by_name' do
36
+ subject { User.sort_by_name }
37
+
38
+ before do
39
+ user.update_attributes(first_name: 'Aaron')
40
+ user_2.update_attributes(first_name: 'Zed')
41
+ end
42
+
43
+ it 'returns a collection of users sorted by first name' do
44
+ should == [user, user_2]
45
+ end
46
+ end
47
+
48
+ describe '#initials' do
49
+ subject { user.initials }
50
+
51
+ before { user.update_attributes(:first_name => 'Aaron', :middle_initial => 'B', :last_name => 'Crenshaw') }
52
+
53
+ it 'returns the initials of the user' do
54
+ should == 'ABC'
55
+ end
56
+ end
57
+
58
+ describe '#work_units_for_day' do
59
+
60
+ before do
61
+ work_unit1.update_attributes(:scheduled_at => Time.now)
62
+ work_unit2.update_attributes(:scheduled_at => 1.days.ago)
63
+ end
64
+
65
+ it 'returns a collection of work units for the user scheduled on a given day' do
66
+ user.work_units_for_day(Time.now).should == [work_unit1]
67
+ end
68
+
69
+ it "sums up all work units' hours for the user scheduled on a given day" do
70
+ work_unit1.update_attributes(:hours => 3)
71
+ work_unit2.update_attributes(:hours => 1, :scheduled_at => Time.now)
72
+ user.hours_entered_for_day(Time.now).should == 4
73
+ end
74
+ end
75
+
76
+ describe '.clients_for_day' do
77
+ subject { user.clients_for_day(Time.now) }
78
+
79
+ before do
80
+ work_unit1.update_attributes(:scheduled_at => Time.now)
81
+ work_unit2.update_attributes(:scheduled_at => Time.now - 1.day)
82
+ end
83
+
84
+ context 'when the user has work units scheduled for a given day' do
85
+ it 'returns a collection of clients for those work units' do
86
+ should == [work_unit1.client]
87
+ end
88
+ end
89
+ end
90
+
91
+ describe '#work_units_for_week' do
92
+ subject { user.work_units_for_week(Time.now) }
93
+
94
+ before do
95
+ work_unit1.update_attributes(:scheduled_at => Time.now)
96
+ work_unit2.update_attributes(:scheduled_at => Time.now - 1.week)
97
+ end
98
+
99
+ it 'returns a collection of work units for the user scheduled during the week of a given day' do
100
+ should == [work_unit1]
101
+ end
102
+ end
103
+
104
+ describe '#unpaid_work_units' do
105
+ subject { user.unpaid_work_units }
106
+
107
+ before do
108
+ work_unit1.update_attributes(:paid => nil, :paid_at => nil)
109
+ work_unit2.update_attributes(:paid => 'True', :paid_at => Time.now)
110
+ work_unit3.update_attributes(:user => user_2)
111
+ end
112
+
113
+ it 'returns a collection of work units which are unpaid and belong to the user' do
114
+ should == [work_unit1]
115
+ end
116
+ end
117
+
118
+ describe '.for_project' do
119
+ let(:project) { Project.make }
120
+ before(:each) { user.has_role!("developer", project) }
121
+
122
+ it 'returns all the users listed on a project' do
123
+ User.for_project(project).should == [user]
124
+ end
125
+ end
126
+
127
+ describe '#to_s' do
128
+ subject { user.to_s }
129
+
130
+ before { user.update_attributes(:first_name => 'Aaron', :middle_initial => 'B', :last_name => 'Crenshaw') }
131
+
132
+ it 'returns the first name, middle initial, and last name for the user' do
133
+ should == 'Aaron B Crenshaw'
134
+ end
135
+ end
136
+
137
+ describe '#admin?' do
138
+ subject { user.admin? }
139
+
140
+ context 'when the user has an admin role' do
141
+ before { user.has_role!(:admin) }
142
+ it { should be_true }
143
+ end
144
+
145
+ context 'when the user does not have an admin role' do
146
+ before { user.has_no_role!(:admin) }
147
+ it { should be_false }
148
+ end
149
+ end
150
+
151
+ describe '#locked' do
152
+ subject { user.locked }
153
+
154
+ context 'when the user is locked' do
155
+ before { user.lock_access! }
156
+ it { should be_true }
157
+ end
158
+
159
+ context 'when the user is unlocked' do
160
+ before { user.unlock_access! }
161
+ it { should be_false }
162
+ end
163
+ end
164
+
165
+ describe '#pto_hours_left' do
166
+ subject { user.pto_hours_left(Date.today.end_of_year) }
167
+
168
+ let(:site_settings) { SiteSettings.make }
169
+
170
+ before do
171
+ site_settings.update_attributes(:total_yearly_pto_per_user => 40)
172
+ work_unit1.update_attributes(:hours => 2, :hours_type => 'PTO', :scheduled_at => Date.today)
173
+ work_unit2.update_attributes(:hours => 3, :hours_type => 'PTO', :scheduled_at => 1.days.from_now)
174
+ work_unit3.update_attributes(:hours => 5, :hours_type => 'PTO', :scheduled_at => '2010-12-31')
175
+ end
176
+
177
+ it 'returns the number of PTO hours left for the given year as of the passed date' do
178
+ should == 35
179
+ end
180
+ end
181
+
182
+ describe 'while being created' do
183
+ it 'creates a new user from the blueprint' do
184
+ lambda do
185
+ User.make
186
+ end.should change(User, :count).by(1)
187
+ end
188
+ end
189
+
190
+ describe 'target_hours_offset' do
191
+ subject { user.target_hours_offset(Date.current) }
192
+
193
+ it 'raises an error on non-date objects' do
194
+ lambda{ user.target_hours_offset(Time.now) }.should raise_error(RuntimeError)
195
+ end
196
+
197
+ it 'calculates the hours a user needs to meet their daily target hours' do
198
+ should == 0.0
199
+ end
200
+ end
201
+
202
+ describe 'expected_hours' do
203
+ subject { user.expected_hours(Date.current).to_s }
204
+
205
+ before do
206
+ work_unit1.update_attributes(:hours => 2, :hours_type => 'Normal', :scheduled_at => Date.yesterday)
207
+ end
208
+
209
+ it 'raises an error if not passed a date' do
210
+ lambda do
211
+ user.expected_hours(Time.now)
212
+ end.should raise_error(RuntimeError)
213
+ end
214
+
215
+ it 'calculates the expected hours for a user' do
216
+ should =~ /\d+/
217
+ end
218
+
219
+ context 'user has been here all year' do
220
+ before do
221
+ work_unit1.update_attributes(:hours => 2, :hours_type => 'Normal', :scheduled_at => Date.current.years_ago(1))
222
+ end
223
+ it { should =~ /\d+/}
224
+ end
225
+ end
226
+
227
+ describe 'percentage_work_for' do
228
+ subject { user.percentage_work_for(Client.new, Date.yesterday, Date.current) }
229
+
230
+ it 'raises an exception if the client is not a Client' do
231
+ lambda do
232
+ user.percentage_work_for(User.new, Date.yesterday, Date.current)
233
+ end.should raise_error(RuntimeError)
234
+ end
235
+
236
+ it 'raises an exception if the start_date is not a Date' do
237
+ lambda do
238
+ user.percentage_work_for(Client.new, 1.days.ago, Date.current)
239
+ end.should raise_error(RuntimeError)
240
+ end
241
+
242
+ it 'raises an exception if the end_date is not a Date' do
243
+ lambda do
244
+ user.percentage_work_for(Client.new, Date.yesterday, Time.now)
245
+ end.should raise_error(RuntimeError)
246
+ end
247
+
248
+ it 'does not raise an exception if given the right parameter types' do
249
+ lambda do
250
+ user.percentage_work_for(client, start_date, end_date)
251
+ end.should_not raise_error(RuntimeError)
252
+ end
253
+
254
+ it 'calculates the percentage of hours worked for a client as a percentage of all work done' do
255
+ should == 0
256
+ end
257
+ end
258
+ end