Miles PayCheck aims to provide employees with a clear and comprehensive understanding of their payroll. The majority of consultants at Miles receive provision-based pay with a base amount, which can vary depending on the project and individual circumstances.
The challenge arises when employees work on projects without a set rate or those not invoiced to customers, but still need to generate provisions. For example, an employee might have a sick child and require leave for a few days each month or work on internal projects. In many cases, the employees should be compensated the same as their main-project for that work.
Additionally, some employees may be involved in multiple projects, complicating the calculation of provisions. The payroll system needs to determine the main project's rate, which may not be immediately clear from the financial records in Xledger.
Miles PayCheck addresses these challenges by making payroll transparent for employees and reducing the error rate before invoicing customers. This ensures that employees have accurate and up-to-date information about their pay, and customers are billed correctly, regardless of the nature of their projects.
Furthermore, Miles PayCheck provides managers and admins with the ability to review all employees' timesheets, offering a comprehensive overview of invoiced amounts versus pay. This enables them to monitor the company's financial health and assist in making informed decisions for future projections and planning.
- Miles PayCheck: Payroll Transparency and Accuracy
- Install dependencies
npm install
- Setup database For example a Local database
- Setup environment variables See section Environment variables
- Run the project
npm run dev
This project is based on the remix-indie stack. We might not use all the stack or have decided on going a different direction, (like google auth instead of handling it ourselves), so there are some parts of the code that should be removed.
For more about the indie-stack. See ## What is in the stack
-section.
Here is a sample of the environment variables that should be set for this project. It is used to set environment variables when running the project locally.
Create a file called .env
in the root of the project and add the following variables.
NODE_ENV=development
BASE_URL="http://localhost:3000"
SESSION_SECRET="xxxxxx"# random string
DATABASE_URL="postgresql://postgres:********@localhost:5432/miles-timelists?schema=public"
#XLEDGER
XLEDGER_TOKEN="xxxxxxxx"
XLEDGER_GRAPHQL_URL="https://www.xledger.net/graphql"
#GOOGLE AUTH
GOOGLE_CLIENT_ID="1000000000000-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxx"
https://www.prisma.io/dataguide/postgresql/setting-up-a-local-postgresql-database
Version 14 is currently used in our Google cloud-instance, so it is recommended to use the same version locally.
Then remember to update your environment variables and run npx prisma migrate dev
to create the tables in the
database.
Unless public, Always check the user's role before rendering a page.
This should be done in every loader
and action
.
http://remix.run/docs/en/1.14.3/guides/styling
Tailwind has been chosen since it generates pure css in the end. You may still write some CSS... I can recommend looking at tailwind-ui-components for learning best-practices with regard to UX. See tailwind-ui https://tailwindui.com/components
Try to keep the users in mind when designing components. Most users will be using mobile devices.
Using tailwind, start with mobile, then think about how it will look on larger devices, adding sm:
, and md:
etc...
https://tailwindcss.com/docs/responsive-design#working-mobile-first
Tip: Start with light mode, then add dark:bg-black
and other classnames to make it look nice.
This is the list of pages and guards that should be implemented for this project. The path is the URL path that should be used for the page. The guard is a description of the logic that should be used to determine if a user should be able to access the page. If a user is not able to access the page, they should be redirected to the error page.
- Path: /login
- Guard: If the user is already logged in, they should be redirected to the home page.
- Path:
/employees
- Guard: Only users who are identified as managers should be able to access this page. If a user is not a manager, they should be redirected to the home page.
- Path:
/employees/{employeeId}/timesheets/{year}/{month}
- Guard: Only users who are identified as employees or managers should be able to access this page. If a user is not an employee or manager, they should be redirected to the home page. Additionally, if a user is a manager, they should only be able to access the timesheet for employees who report to them. (NOT IMPLEMENTED)
To calculate the pay for a given employee, you need to know the following:
Project + activity. How many hours and at what rate.
Each employee has some variables that are used to calculate the pay:
- Yearly fixed salary (Fetched from xledger)
- Self-cost factor (Stored in a table)
- Provision percentage (Stored in a table)
The above can be edited by the manager.
We can then calculate the pay for an employee as follows:
Monthly fixed salary = Yearly fixed salary / 12
Monthly invoiced turnover = SUM (Hours * hourly rate) // note calculation is done per project
Monthly self-cost = Monthly fixed salary * self-cost factor
Net amount invoiced that exceeds self-cost = MAX (Monthly invoiced turnover - self-cost; 0)
Monthly provision = (Monthly invoiced turnover - self-cost) * provision percentage
Monthly pay = Monthly fixed salary + Monthly provision
The following tasks have special rules for calculating the pay:
Code | Task | Comment |
---|---|---|
S-101 | Fagsamtaler | "Main project" |
S-102 | Faglig intervju | 1000 kr an hour |
S-103 | Bistand innsalg | "Main project" |
S-104 | Profilaktiviteter | "Main project" |
S-105 | Mentor | "Main project" |
S-106 | Fagtjener | "Main project" |
S-107 | Verneplikt | 50% of "Main project" |
S-108 | Interntim m/p | "Main project" |
992 | Syk, egenmelding | "Main project" |
993 | Sykt | "Main project" |
994 | Sykemelding | "Main project" |
995 | Foreldrepermisjon | "Main project" |
996 | Annen lønnet permisjon | "Main project" |
As you see above, most tasks are part of the "Main project". The "Main project" is the project that the employee has worked the most hours for the given month.
Note, that the "Main project" is not necessarily the project with the highest pay but the project with the most hours.
This project is based on the remix-indie stack as shown below. We might not use all the stack or have decided on going a different direction, (like google auth instead of handling it ourselves), so there are some parts of the code that should be removed.
- Fly app deployment with Docker
- Production-ready SQLite Database
- Healthcheck endpoint for Fly backups region fallbacks
- GitHub Actions for deploy on merge to production and staging environments
- Email/Password Authentication with cookie-based sessions
- Database ORM with Prisma
- Styling with Tailwind
- End-to-end testing with Cypress
- Local third party request mocking with MSW
- Unit testing with Vitest and Testing Library
- Code formatting with Prettier
- Linting with ESLint
- Static Types with TypeScript
Not a fan of the stack? Fork it, change it, and use npx create-remix --template your/repo
! Make it your own.
Click this button to create a Gitpod workspace with the project set up and Fly pre-installed
-
This step only applies if you have opted out of having the CLI install dependencies for you:
npx remix init
-
Initial setup: If you just generated this project, this step has been done for you.
npm run setup
-
Start dev server:
npm run dev
This starts your app in development mode, rebuilding assets on file changes.
The database seed script creates a new user with some data you can use to get started:
- Email:
rachel@remix.run
- Password:
racheliscool
This is a pretty simple note-taking app, but it is a good example of how you can build a full stack app with Prisma and Remix. The main functionality is creating users, logging in and out, and creating and deleting notes.
- creating users, and logging in and out ./app/models/user.server.ts
- user sessions, and verifying them ./app/session.server.ts
- creating, and deleting notes ./app/models/note.server.ts
This Remix Stack comes with two GitHub Actions that handle automatically deploying your app to production and staging environments.
Prior to your first deployment, you will need to do a few things:
-
Sign up and log in to Fly
fly auth signup
Note: If you have more than one Fly account, ensure that you're signed into the same account in the Fly CLI as you're in the browser. In your terminal, run
fly auth whoami
and ensure the email matches the Fly account signed into the browser. -
Create two apps on Fly, one for staging and one for production:
fly apps create miles-dash-c878 fly apps create miles-dash-c878-staging
Note: Make sure this name matches the
app
set in yourfly.toml
file. Otherwise, you will not be able to deploy.- Initialize Git.
git init
-
Create a new GitHub Repository, and then add it as the remote for your project. Don't push your app yet!
git remote add origin <ORIGIN_URL>
-
Add a
FLY_API_TOKEN
to your GitHub repo. To do this, go to your user settings on Fly and create a new token, then add it to your repo secrets with the nameFLY_API_TOKEN
. -
Add a
SESSION_SECRET
to your fly app secrets, to do this you can run the following commands:fly secrets set SESSION_SECRET=$(openssl rand -hex 32) --app miles-dash-c878 fly secrets set SESSION_SECRET=$(openssl rand -hex 32) --app miles-dash-c878-staging
If you don't have openssl installed, you can also use 1password to generate a random secret, just replace
$(openssl rand -hex 32)
with the generated secret. -
Create a persistent volume for the sqlite database for both your staging and production environments. Run the following:
fly volumes create data --size 1 --app miles-dash-c878 fly volumes create data --size 1 --app miles-dash-c878-staging
Now that everything is set up you can commit and push your changes to your repo. Every commit to your main
branch will
trigger a deployment to your production environment, and every commit to your dev
branch will trigger a deployment to
your staging environment.
The sqlite database lives at /data/sqlite.db
in your deployed application. You can connect to the live database by
running fly ssh console -C database-cli
.
If you run into any issues deploying to Fly, make sure you've followed all the steps above and if you have, then post as many details about your deployment (including your app name) to the Fly support community. They are normally pretty responsive over there and hopefully can help resolve any of your deployment issues and questions.
We use GitHub Actions for continuous integration and deployment. Anything that gets into the main
branch will be
deployed to production after running tests/build/etc. Anything in the dev
branch will be deployed to staging.
We use Cypress for our End-to-End tests in this project. You will find those in the cypress
directory. As you make
changes, add to an existing file or create a new file in the cypress/e2e
directory to test your changes.
We use @testing-library/cypress
for selecting elements on the page
semantically.
To run these tests in development, run npm run test:e2e:dev
which will start the dev server for the app as well as the
Cypress client. Make sure the database is running in docker as described above.
We have a utility for testing authenticated features without having to go through the login flow:
cy.login();
// you are now logged in as a new user
We also have a utility to auto-delete the user at the end of your test. Just make sure to add this in each test file:
afterEach(() => {
cy.cleanupUser();
});
That way, we can keep your local db clean and keep your tests isolated from one another.
For lower level tests of utilities and individual components, we use vitest
. We have DOM-specific assertion helpers
via @testing-library/jest-dom
.
This project uses TypeScript. It's recommended to get TypeScript set up for your editor to get a really great in-editor
experience with type checking and auto-complete. To run type checking across the whole project, run npm run typecheck
.
This project uses ESLint for linting. That is configured in .eslintrc.js
.
We use Prettier for auto-formatting in this project. It's recommended to install an editor
plugin (like the VSCode Prettier plugin)
to get auto-formatting on save. There is also a npm run format
script you can run to format all files in the project.
If you don't have access to Xledger, you can use mock data instead.
Set environment variable MOCK_DATA=TRUE
.
When logging in, use your normal miles-account. The mock-data will be used instead of fetching data from xledger. Your xledger id will be set to 999 unless you already have a user that has been assigned an id.