Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .claude/skills/feature-branch-extraction/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
name: feature-branch-extraction
description: "Extracts a set of files from develop into a dedicated feature branch so the feature branch is ahead of develop and a PR can be opened. Activates whenever the user asks to move, extract, or isolate files from develop into a feature branch, or when a branch is created before files are removed from develop."
license: MIT
metadata:
author: project
---

# Feature Branch Extraction

## The Problem

When you create a feature branch **before** removing its files from `develop`, the branch ends up *behind* develop after the removal commit lands. The branch and develop share the same history, so there is no diff and no PR can be opened.

```
develop: A → B → C (removes files)
feature/X: A → B ← behind develop, nothing to PR
```

## Correct Procedure

Always follow these steps in order.

### 1. Identify the extraction commit on develop

Find the SHA of the commit on `develop` that removed the files (call it `REMOVE_SHA`) and the SHA just before it that still has the files (call it `BEFORE_SHA`):

```bash
git log --oneline develop | head -10
# REMOVE_SHA chore: extract X to feature/X
# BEFORE_SHA ran pint / previous commit
```

### 2. Create the feature branch pointing at REMOVE_SHA

If the branch does not exist yet:

```bash
git checkout -b feature/X
git push -u origin feature/X
git checkout develop
```

If the branch already exists but is behind develop (the mistake already happened):

```bash
git checkout feature/X
git reset --hard REMOVE_SHA # move tip to develop's current HEAD
```

### 3. Restore the extracted files onto the feature branch

While on `feature/X`, check out every file/directory that was removed in REMOVE_SHA from BEFORE_SHA:

```bash
git checkout BEFORE_SHA -- path/to/file1 path/to/dir/ ...
```

This stages the files as additions on top of develop's current state.

> Also restore any files that were *modified* by the removal commit (e.g. List pages
> that had export actions stripped, or a ServiceProvider that had a command registration
> removed). Check these out from BEFORE_SHA too so the feature branch has the full
> original version.

### 4. Commit and force-push

```bash
git commit -m "feat: add <feature name>"
git push --force-with-lease origin feature/X
```

After this, the graph looks like:

```
develop: A → B → REMOVE_SHA
feature/X: A → B → REMOVE_SHA → NEW_SHA (adds files)
```

`feature/X` is one commit ahead of `develop`, and a PR can be opened.

## Checklist

- [ ] Identified REMOVE_SHA (the deletion commit on develop)
- [ ] Identified BEFORE_SHA (the commit before the deletion)
- [ ] Feature branch tip is at REMOVE_SHA (`git reset --hard REMOVE_SHA`)
- [ ] All deleted files restored with `git checkout BEFORE_SHA -- <paths>`
- [ ] All modified files (stripped pages, providers) also restored from BEFORE_SHA
- [ ] New commit created on feature branch
- [ ] Force-pushed to remote (`--force-with-lease`)
- [ ] PR can be opened (feature branch is ahead of develop)

## Anti-patterns to avoid

- **Creating the feature branch first, then removing from develop.** This always produces a branch that is behind develop.
- **`git checkout BEFORE_SHA -- .`** (restoring the entire tree). This would make the feature branch identical to BEFORE_SHA and reintroduce all the commits develop made after that point.
- **Merging develop into feature/X** to catch up. This produces a merge commit and pollutes the diff with unrelated changes.
31 changes: 24 additions & 7 deletions .github/DOCKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,38 @@ Visit: http://localhost:8080 (override the port with `APP_PORT` in `.env`).

Both PHP images ship the full extension set the app needs: `intl`, `gd`,
`pdo_mysql`, `bcmath`, `zip`, `exif`, `soap`, `redis`. The CLI image also has
Composer, a 1G memory limit for the test suite, and bundled `pdo_sqlite`
(the suite runs on an in-memory sqlite database — no db service needed for
tests).
Composer and a 1G memory limit for the test suite.

---

## Running the test suite

```bash
docker compose run --rm cli vendor/bin/phpunit --exclude-group failing,troubleshooting
docker compose run --rm cli php artisan test --exclude-group failing,troubleshooting
```

`APP_ENV=testing` is the `cli` service default, so `.env.testing`
(sqlite `:memory:`) is picked up automatically. See `RUNNING_TESTS.md` for
filters, groups, and suites.
Use `php artisan test`, not `vendor/bin/phpunit` directly — the two have been observed to behave
differently for this app: a raw `vendor/bin/phpunit` run silently drops some submitted field
values in Livewire form tests. `artisan test` is the proven-reliable path and is what CI uses, so
standardize on it.

**Known issue (see [#689](https://github.com/InvoicePlane/InvoicePlane-v2/issues/689)):** a
freshly-`docker compose build`'t `cli` image has, at least once, reproduced this same
field-dropping bug at scale (100+ false failures) even under `artisan test`, for reasons not yet
isolated — despite extension/ini parity with a known-good image. Before trusting a full local run
from a rebuilt `cli` image, sanity-check it against a small, known test first, e.g.:
```bash
docker compose run --rm cli php artisan test --filter=ContactsTest
```
All 11 assertions should pass. If any fail with "field is required" errors on data you know you
supplied, don't trust the rest of that run — see the linked issue.

`APP_ENV=testing` is the `cli` service default, and it always connects to
the compose stack's real `db` service (MariaDB) for tests — the `cli`
service injects `DB_CONNECTION=mysql`/`DB_HOST=db`/etc. itself, so nothing
in `.env.testing` needs editing. This intentionally does not fall back to
SQLite: SQLite's lenient identifier quoting has masked real bugs before that
only surfaced against MariaDB in CI.

### File ownership on Linux

Expand Down
82 changes: 41 additions & 41 deletions .github/IMPORTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ InvoicePlane supports importing data from external systems using CSV files. This

---

## 📂 Accessing the Import Tool
## Accessing the Import Tool

1. Navigate to **Settings**.
2. Click on **Import Data**.

---

## 📄 Import Requirements
## Import Requirements

To ensure a successful import:

Expand All @@ -26,72 +26,72 @@ To ensure a successful import:

---

## 📁 Supported Files and Structures
## Supported Files and Structures

### 1. `customers.csv`

| Column Name | Description |
| Column Name | Description |
|---------------------|-------------------------------------|
| `client_name` | Customer's full name |
| `client_address_1` | Primary address line |
| `client_address_2` | Secondary address line |
| `client_city` | City |
| `client_state` | State or province |
| `client_zip` | ZIP or postal code |
| `client_country` | Country |
| `client_phone` | Phone number |
| `client_fax` | Fax number |
| `client_mobile` | Mobile number |
| `client_email` | Email address |
| `client_web` | Website URL |
| `client_vat_id` | VAT identification number |
| `client_tax_code` | Tax code |
| `client_active` | Status (`1` for active, `0` for inactive) |
| `client_name` | Customer's full name |
| `client_address_1` | Primary address line |
| `client_address_2` | Secondary address line |
| `client_city` | City |
| `client_state` | State or province |
| `client_zip` | ZIP or postal code |
| `client_country` | Country |
| `client_phone` | Phone number |
| `client_fax` | Fax number |
| `client_mobile` | Mobile number |
| `client_email` | Email address |
| `client_web` | Website URL |
| `client_vat_id` | VAT identification number |
| `client_tax_code` | Tax code |
| `client_active` | Status (`1` for active, `0` for inactive) |

### 2. `invoices.csv`

| Column Name | Description |
| Column Name | Description |
|-------------------------|-------------------------------------------|
| `user_email` | Email of the InvoicePlane user |
| `client_name` | Name of the customer |
| `invoice_date_created` | Creation date (`YYYY-MM-DD`) |
| `invoice_date_due` | Due date (`YYYY-MM-DD`) |
| `invoice_number` | Unique invoice number |
| `invoice_terms` | Payment terms |
| `user_email` | Email of the InvoicePlane user |
| `client_name` | Name of the customer |
| `invoice_date_created` | Creation date (`YYYY-MM-DD`) |
| `invoice_date_due` | Due date (`YYYY-MM-DD`) |
| `invoice_number` | Unique invoice number |
| `invoice_terms` | Payment terms |

### 3. `invoice_items.csv`

| Column Name | Description |
| Column Name | Description |
|--------------------|-------------------------------------------|
| `invoice_number` | Associated invoice number |
| `item_tax_rate` | Tax rate (e.g., `7.8` for 7.8%) |
| `item_date_added` | Date added (`YYYY-MM-DD`) |
| `item_name` | Name of the item |
| `item_description` | Description of the item |
| `item_quantity` | Quantity of the item |
| `item_price` | Price per item (numeric, no currency symbols) |
| `invoice_number` | Associated invoice number |
| `item_tax_rate` | Tax rate (e.g., `7.8` for 7.8%) |
| `item_date_added` | Date added (`YYYY-MM-DD`) |
| `item_name` | Name of the item |
| `item_description` | Description of the item |
| `item_quantity` | Quantity of the item |
| `item_price` | Price per item (numeric, no currency symbols) |

### 4. `payments.csv`

| Column Name | Description |
| Column Name | Description |
|------------------|-------------------------------------------|
| `invoice_number` | Associated invoice number |
| `payment_method` | Method of payment (e.g., Cash, Credit) |
| `payment_date` | Date of payment (`YYYY-MM-DD`) |
| `invoice_number` | Associated invoice number |
| `payment_method` | Method of payment (e.g., Cash, Credit) |
| `payment_date` | Date of payment (`YYYY-MM-DD`) |
| `payment_amount` | Amount paid (numeric, no currency symbols)|
| `payment_note` | Additional notes |
| `payment_note` | Additional notes |

---

## ⚠️ Important Notes
## Important Notes

- **Custom Fields**: Importing custom fields is not supported in the current version.
- **Data Validation**: Ensure all data is accurate and conforms to the required formats to prevent import errors.
- **Testing**: It's recommended to test imports with a small dataset before full-scale importing.

---

## 🛠️ Troubleshooting
## Troubleshooting

- **Import Errors**: If the import process fails, double-check file formats, headers, and data consistency.
- **Community Support**: For assistance, visit the [InvoicePlane Community Forums](https://community.invoiceplane.com/).
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,18 @@ jobs:
DB_DATABASE: invoiceplane_test
DB_USERNAME: root
DB_PASSWORD: root
IMPORT_DB_HOST: 127.0.0.1
IMPORT_DB_PORT: 3306
IMPORT_DB_DATABASE: invoiceplane_v1_import
IMPORT_DB_USERNAME: root
IMPORT_DB_PASSWORD: root

steps:
- uses: actions/checkout@v4

- name: Install MariaDB client
run: sudo apt-get update && sudo apt-get install -y mariadb-client

- name: Setup PHP with Composer
uses: ./.github/actions/setup-php-composer
with:
Expand Down
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,15 @@ actual-real-resolved-issues.md
current-issues.md
merge-order.md
"saving some issues.md"
issues_full.json
refine-issues.json
/plans/
feature-parity.md
issues_index.json
parity-results.md
report-2026-07-18.md
results-transcript.md
summary-feature-parity.md
plan-2026-07-19.md
storage/dompdf_log
untouched.json
5 changes: 2 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ Laravel 11 + Filament v4 + Livewire v3 invoicing app. Modular architecture via `
composer install
cp .env.example .env && php artisan key:generate
php artisan migrate && php artisan db:seed
# Tests (no MySQL locally? use SQLite)
# Tests run against real MariaDB — no SQLite fallback (parity with CI)
cp .env.testing.example .env.testing
# set DB_CONNECTION=sqlite, DB_DATABASE=:memory: in .env.testing
php artisan test
docker compose run --rm cli php artisan test --exclude-group failing,troubleshooting
```

---
Expand Down
26 changes: 22 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@

## What this is

Laravel 11 + Filament v4 + Livewire v3 invoicing app. Modular architecture via `nwidart/laravel-modules`. PHP 8.1+ enums, Spatie roles/permissions, multi-tenancy via Filament's built-in tenant system scoped to `Company`.
Laravel 13 + Filament v5 + Livewire v4 invoicing app. Modular architecture via `nwidart/laravel-modules` v13. PHP 8.3+ (dev box: 8.4.23), Spatie roles/permissions, multi-tenancy via Filament's built-in tenant system scoped to `Company`.

**Resolved versions** (from `composer.lock`):
- `laravel/framework` 13.15.0 (PHP ^8.3)
- `filament/filament` 5.6.7 (+ 9 sub-packages @ 5.6.7)
- `livewire/livewire` 4.3.1
- `nwidart/laravel-modules` 13.0.0
- `spatie/laravel-permission` 8.0.0
- `danharrin/livewire-rate-limiting` 2.2.0

---

Expand Down Expand Up @@ -187,11 +195,21 @@ User::factory()->create(['is_active' => true, 'email_verified_at' => now()])

### DB for tests

Tests need a DB. Production CI uses MariaDB 11. For local dev without MySQL, set in `.env.testing`:
Tests need a real MariaDB DB — matching CI (MariaDB 11) — not SQLite. SQLite's lenient identifier
quoting has silently masked real bugs before (e.g. `->latest()` defaulting to a nonexistent
`created_at` column on `$timestamps = false` models passed locally, failed on CI). Run via the
`cli` compose service, which points at the stack's `db` service automatically:
```
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
docker compose run --rm cli php artisan test --exclude-group failing,troubleshooting
```
No `.env.testing` edits needed — the `cli` service injects `DB_CONNECTION=mysql`/`DB_HOST=db` etc.
itself. Use `php artisan test`, not `vendor/bin/phpunit` directly — the two have been observed to
behave differently for this app's Livewire form tests; `artisan test` is the reliable one.
**Known issue:** a freshly-rebuilt `cli` image has reproduced false Livewire-form failures at
scale even under `artisan test`, for reasons not yet isolated — see
[#689](https://github.com/InvoicePlane/InvoicePlane-v2/issues/689) and sanity-check with
`--filter=ContactsTest` (should be 11/11 passing) before trusting a full run from a rebuilt image.
See `.github/DOCKER.md`.

### AAA phase comment style

Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
## InvoicePlane v2 — Development Makefile
## ──────────────────────────────────────────────────────────────────────────────
##
## NOTE: `vendor/bin/phpunit` (used by the targets below) and `php artisan
## test` (make artisan-test) have been observed to behave differently for
## this app — a raw phpunit run has silently dropped submitted field values
## in Livewire form tests in some environments. If a target below reports a
## failure that `make artisan-filter FILTER="..."` doesn't reproduce, prefer
## the artisan-test variant; it matches what CI runs.
##
## QUICK START
## make test Run the full PHPUnit suite (all tests)
## make smoke Run only @group smoke tests (fast sanity check)
Expand Down
Loading
Loading