Skip to content

Commit c1e36ff

Browse files
authored
Merge branch 'main' into devin/1751473200-dividend-monthly-report
2 parents 0e7e193 + 8582efb commit c1e36ff

File tree

116 files changed

+2848
-783
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+2848
-783
lines changed

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ frontend/utils/routes.*
22
backend
33
vendor
44
tmp
5-
frontend/.next
5+
frontend/.next
6+
.github

CONTRIBUTING.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Contributing to Flexile
2+
3+
Thanks for your interest in contributing! This document will help you get started.
4+
5+
## Quick Start
6+
7+
1. Set up the repository
8+
9+
```bash
10+
git clone https://github.com/antiwork/flexile.git
11+
```
12+
13+
2. Set up your development environment
14+
15+
For detailed instructions on setting up your local development environment, please refer to our [README](README.md).
16+
17+
## Development
18+
19+
1. Create your feature branch
20+
21+
```bash
22+
git checkout -b feature/your-feature
23+
```
24+
25+
2. Start the development environment
26+
27+
```bash
28+
bin/dev
29+
```
30+
31+
3. Run the test suite
32+
33+
```bash
34+
# Run Rails specs
35+
bundle exec rspec
36+
37+
# Run Playwright end-to-end tests
38+
pnpm playwright test
39+
```
40+
41+
## Testing Guidelines
42+
43+
- Write descriptive test names that explain the behavior being tested
44+
- Keep tests independent and isolated
45+
- For API endpoints, test response status, format, and content
46+
- Use factories for test data instead of creating objects directly
47+
- Test both happy path and edge cases
48+
49+
## Pull Request
50+
51+
1. Update documentation if you're changing behavior
52+
2. Add or update tests for your changes
53+
3. Include screenshots of your test suite passing locally
54+
4. Make sure all tests pass
55+
5. Request a review from maintainers
56+
6. After reviews begin, avoid force-pushing to your branch
57+
- Force-pushing rewrites history and makes review threads hard to follow
58+
- Don't worry about messy commits - we squash everything when merging to main
59+
7. The PR will be merged once you have the sign-off of at least one other developer
60+
61+
## Style Guide
62+
63+
- Follow the existing code patterns
64+
- Use clear, descriptive variable names
65+
- Write TypeScript for frontend code
66+
- Follow Ruby conventions for backend code
67+
68+
## Writing Bug Reports
69+
70+
A great bug report includes:
71+
72+
- A quick summary and/or background
73+
- Steps to reproduce
74+
- Be specific!
75+
- Give sample code if you can
76+
- What you expected would happen
77+
- What actually happens
78+
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
79+
80+
## Help
81+
82+
- Check existing discussions/issues/PRs before creating new ones
83+
- Start a discussion for questions or ideas
84+
- Open an [issue](https://github.com/antiwork/flexile/issues) for bugs or problems
85+
86+
## License
87+
88+
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE.md).

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The easiest way to set up the development environment is to use the [`bin/setup`
1616

1717
- Set up Ruby (ideally using `rbenv`/`rvm`) and PostgreSQL
1818
- Install dependencies using `pnpm i` and `cd backend && bundle i`
19-
- Set up your environment by either using `pnpx vercel env pull` or `cp .env.example .env` and filling in missing values and your own keys
19+
- Set up your environment by either using `pnpx vercel env pull .env` or `cp .env.example .env` and filling in missing values and your own keys
2020
- Run `cd backend && gem install foreman`
2121

2222
## Running the App

backend/app/controllers/concerns/set_current.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def set_current
4545
default_currency: "USD"
4646
)
4747
user.company_administrators.create!(company: company)
48+
user.company_administrators.reload
49+
user.companies.reload
4850
end
4951
end
5052
end

backend/app/controllers/internal/companies/invoices_controller.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def microdeposit_verification_details
6464
def export
6565
authorize Invoice
6666

67-
body = InvoiceCsv.new(Current.company.invoices.order(created_at: :asc)).generate
67+
body = InvoiceCsv.new(Current.company.invoices.alive.order(created_at: :asc)).generate
6868
response.headers["Content-Disposition"] = "attachment; filename=invoices-#{Time.current.strftime("%Y-%m-%d_%H%M%S")}.csv"
6969
render body:, content_type: "text/csv"
7070
end
@@ -99,14 +99,22 @@ def reject
9999
).perform
100100
end
101101

102+
def destroy
103+
invoice = Current.user.invoices.alive.find_by!(external_id: params[:id])
104+
authorize invoice
105+
invoice.mark_deleted!
106+
107+
head :no_content
108+
end
109+
102110
private
103111
def load_invoice!
104-
@invoice = Current.user.invoices.find_by!(external_id: params[:id])
112+
@invoice = Current.user.invoices.alive.find_by!(external_id: params[:id])
105113
end
106114

107115
def authorize_invoices_for_rejection
108116
all_invoices_belong_to_company = invoice_external_ids_for_rejection.all? do |invoice_external_id|
109-
Current.company.invoices.exists?(external_id: invoice_external_id)
117+
Current.company.invoices.alive.exists?(external_id: invoice_external_id)
110118
end
111119

112120
unless all_invoices_belong_to_company
@@ -117,7 +125,7 @@ def authorize_invoices_for_rejection
117125
def authorize_invoices_for_approval_and_pay
118126
ids = invoice_external_ids_for_approval + invoice_external_ids_for_payment
119127
all_invoices_belong_to_company = ids.all? do |invoice_id|
120-
Current.company.invoices.exists?(external_id: invoice_id)
128+
Current.company.invoices.alive.exists?(external_id: invoice_id)
121129
end
122130

123131
unless all_invoices_belong_to_company

backend/app/controllers/internal/companies/workers_controller.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@ def create
1717
).perform
1818

1919
if result[:success]
20-
render json: { success: true, new_user_id: result[:company_worker].user_id, document_id: result[:document].id }, status: :ok
20+
render json: { success: true, new_user_id: result[:company_worker].user_id, document_id: result[:document]&.id }, status: :ok
2121
else
2222
render json: result, status: :unprocessable_entity
2323
end
2424
end
2525

2626
private
2727
def worker_params
28-
permitted_keys = %i[email started_at pay_rate_type pay_rate_in_subunits role]
29-
permitted_keys << :hours_per_week if params.dig(:contractor, :pay_rate_type) == "hourly"
30-
params.require(:contractor).permit(*permitted_keys)
28+
params.require(:contractor).permit(:email, :started_at, :pay_rate_in_subunits, :role, :pay_rate_type, :contract_signed_elsewhere)
3129
end
3230
end

backend/app/dashboards/company_worker_dashboard.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ class CompanyWorkerDashboard < Administrate::BaseDashboard
1414
user: Field::BelongsTo,
1515
id: Field::Number,
1616
started_at: Field::DateTime,
17-
hours_per_week: Field::Number,
1817
pay_rate_in_subunits: Field::Number,
1918
role: Field::String,
2019
created_at: Field::DateTime,
@@ -40,7 +39,6 @@ class CompanyWorkerDashboard < Administrate::BaseDashboard
4039
user
4140
id
4241
started_at
43-
hours_per_week
4442
pay_rate_in_subunits
4543
role
4644
created_at
@@ -54,7 +52,6 @@ class CompanyWorkerDashboard < Administrate::BaseDashboard
5452
company
5553
user
5654
started_at
57-
hours_per_week
5855
pay_rate_in_subunits
5956
role
6057
].freeze

backend/app/mailers/company_mailer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def consolidated_invoice_receipt(user_id:, consolidated_payment_id:, processed_d
2828
user = User.find(user_id)
2929
consolidated_payment = ConsolidatedPayment.find(consolidated_payment_id)
3030
@consolidated_invoice = consolidated_payment.consolidated_invoice
31-
contractors = User.where(id: @consolidated_invoice.invoices.pluck(:user_id))
31+
contractors = User.where(id: @consolidated_invoice.invoices.alive.pluck(:user_id))
3232
@contractor_count = contractors.count
3333
@country_count = contractors.pluck(:country_code).uniq.count
3434
@bank_account_last_four = consolidated_payment.bank_account_last_four

backend/app/mailers/company_worker_mailer.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def invoice_rejected(invoice_id:, reason: nil)
4747
end
4848

4949
def invoice_approved(invoice_id:)
50-
@invoice = Invoice.find(invoice_id)
50+
@invoice = Invoice.alive.find_by(id: invoice_id)
51+
return if @invoice.nil?
5152
@company = @invoice.company
5253
@user = @invoice.user
5354
@bank_account = @user.bank_account

backend/app/models/company.rb

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ class Company < ApplicationRecord
2626
self.const_set("ACCESS_ROLE_#{access_role.upcase}", access_role)
2727
end
2828

29+
ADMIN_CHECKLIST_ITEMS = [
30+
{ key: "add_company_details", title: "Add company details", description: "Add your company name and basic information" },
31+
{ key: "add_bank_account", title: "Add bank account", description: "Connect your bank account to enable payments" },
32+
{ key: "invite_contractor", title: "Invite a contractor", description: "Add your first team member" },
33+
{ key: "send_first_payment", title: "Send your first payment", description: "Process your first contractor payment" }
34+
].freeze
35+
36+
WORKER_CHECKLIST_ITEMS = [
37+
{ key: "fill_tax_information", title: "Fill tax information", description: "Complete your tax details" },
38+
{ key: "add_payout_information", title: "Add payout information", description: "Set up your payment method" },
39+
{ key: "sign_contract", title: "Sign contract", description: "Review and sign your contractor agreement" }
40+
].freeze
41+
2942
has_many :company_administrators
3043
has_many :administrators, through: :company_administrators, source: :user
3144
has_many :company_lawyers
@@ -98,7 +111,6 @@ def active
98111
after_create_commit :create_balance!
99112
after_update_commit :update_convertible_implied_shares, if: :saved_change_to_fully_diluted_shares?
100113

101-
102114
accepts_nested_attributes_for :expense_categories
103115

104116
delegate :stripe_setup_intent, :bank_account_last_four, :microdeposit_verification_required?,
@@ -135,7 +147,7 @@ def has_sufficient_balance?(usd_amount)
135147
account_balance >= (is_trusted? ? 0 : usd_amount)
136148
end
137149

138-
def pending_invoice_cash_amount_in_cents = invoices.pending.sum(:cash_amount_in_cents)
150+
def pending_invoice_cash_amount_in_cents = invoices.alive.pending.sum(:cash_amount_in_cents)
139151

140152
def create_stripe_setup_intent
141153
Stripe::SetupIntent.create({
@@ -190,6 +202,28 @@ def json_flag?(flag)
190202
json_data&.dig("flags")&.include?(flag)
191203
end
192204

205+
def checklist_items(user)
206+
case user
207+
when CompanyAdministrator
208+
ADMIN_CHECKLIST_ITEMS.map do |item|
209+
item.merge(completed: checklist_item_completed?(item[:key], user))
210+
end
211+
when CompanyWorker
212+
WORKER_CHECKLIST_ITEMS.map do |item|
213+
item.merge(completed: checklist_item_completed?(item[:key], user))
214+
end
215+
else
216+
[]
217+
end
218+
end
219+
220+
def checklist_completion_percentage(user)
221+
completed_count = checklist_items(user).count { |item| item[:completed] }
222+
return 0 if checklist_items(user).empty?
223+
224+
(completed_count.to_f / checklist_items(user).size * 100).round
225+
end
226+
193227
private
194228
def update_convertible_implied_shares
195229
convertible_investments.each do |investment|
@@ -211,4 +245,25 @@ def fetch_or_create_stripe_customer_id!
211245
update!(stripe_customer_id: stripe_customer.id)
212246
stripe_customer_id
213247
end
248+
249+
def checklist_item_completed?(key, user)
250+
case key
251+
when "add_company_details"
252+
name.present?
253+
when "add_bank_account"
254+
bank_account_ready?
255+
when "invite_contractor"
256+
company_workers.active.exists?
257+
when "send_first_payment"
258+
invoices.where(status: Invoice::PAID_OR_PAYING_STATES).exists?
259+
when "fill_tax_information"
260+
user.user.compliance_info&.tax_information_confirmed_at.present?
261+
when "add_payout_information"
262+
user.user.bank_account.present?
263+
when "sign_contract"
264+
user.contract_signed?
265+
else
266+
false
267+
end
268+
end
214269
end

0 commit comments

Comments
 (0)