Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Default reviewers for every change in this repo.
# Outside contributors: fork the repo and open a PR against the default branch
# Outside contributors: fork the repo and open a PR against the default branch,
# you do NOT need write access. A code owner below must approve before merge.
* @zerp-pk/maintainers
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ User and developer documentation for [Zerp](https://github.com/zerp-pk/zerp), bu

Two doc sets live side by side:

- `docs-user/` User Docs (served at `/docs`)
- `docs-developer/` Developer Docs (served at `/developer`)
- `docs-user/` - User Docs (served at `/docs`)
- `docs-developer/` - Developer Docs (served at `/developer`)

## Local development

Expand Down
16 changes: 8 additions & 8 deletions docs-developer/10-getting-started/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar_position: 2

This is a condensed walkthrough. The
[`zerp-pk/zerp` README](https://github.com/zerp-pk/zerp#readme) is the
source of truth check it if anything here goes stale.
source of truth - check it if anything here goes stale.

## Prerequisites

Expand All @@ -21,7 +21,7 @@ core app, matching what `composer.json`'s path repositories expect:

```
some-folder/
├── zerp/ # core app github.com/zerp-pk/zerp
├── zerp/ # core app - github.com/zerp-pk/zerp
└── ZerpPackages/
├── hrm/ # github.com/zerp-pk/hrm
├── account/ # github.com/zerp-pk/account
Expand All @@ -30,7 +30,7 @@ some-folder/
```

The full module → repository table is in the core app's README. You don't
need every module for local development only clone the ones you're
need every module for local development - only clone the ones you're
working on or testing against; `composer install` just won't symlink the
ones that are missing.

Expand All @@ -54,7 +54,7 @@ php artisan storage:link
```

This runs `migrate:fresh` (drops all tables) then prompts for a module
preset `Full Suite`, `HR Only`, `Sales & CRM`, or a `Custom selection`
preset - `Full Suite`, `HR Only`, `Sales & CRM`, or a `Custom selection`
picker (see `config/module-presets.php` in the core app). Every module
found under `vendor/zerp/*` still gets registered into the `add_ons`
table via `PackageSeeder`, but only the ones you select are enabled and
Expand All @@ -72,7 +72,7 @@ npm run dev
Visit `http://localhost:8000`, log in with the seeded super-admin
(`company@example.com` / `1234`).

## Module images are symlinks know this before you debug them
## Module images are symlinks - know this before you debug them

Each module ships its own images (favicon, and for the Landing Page module the
hero/gallery/module art). They are **not copied** into `public/`. At module
Expand All @@ -92,7 +92,7 @@ So a working image URL depends on a two-hop symlink chain. Two consequences:
fresh clone inherited symlinks pointing at the original author's home
directory, and *every module image 404'd*. If you ever see links like
`/home/someone/...` in `public/packages`, your checkout predates the fix.
- **A module you didn't clone leaves a dangling link.** That's harmless only
- **A module you didn't clone leaves a dangling link.** That's harmless - only
that module's images 404.

Images not loading? Check for dangling links first, then republish:
Expand All @@ -112,7 +112,7 @@ re-run. Note it only runs on install, so nothing repairs these automatically.
Two related traps:

- `php artisan storage:link` covers `public/storage` (uploaded media, served via
`imageUrlPrefix`). That's a **separate** link from the ones above having one
`imageUrlPrefix`). That's a **separate** link from the ones above - having one
working tells you nothing about the other.
- **On Windows**, git only materialises real symlinks with Developer Mode or an
elevated shell; otherwise it checks them out as small text files containing the
Expand All @@ -121,7 +121,7 @@ Two related traps:

## Prefer Docker?

`docker compose up -d --build` from the core app repo works the same way
`docker compose up -d --build` from the core app repo works the same way,
see [Docker & CI](../30-guides/docker-and-ci.md). The sibling `ZerpPackages/` checkout is
still required either way, since the Docker build context is the **parent**
directory of `zerp/`.
4 changes: 2 additions & 2 deletions docs-developer/10-getting-started/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar_position: 1

Zerp is a Laravel 12 + Inertia.js + React (TypeScript) ERP/SaaS platform. The
[main app](https://github.com/zerp-pk/zerp) is a thin core (auth, tenancy,
billing, the Media Library, module loader) almost every business feature
billing, the Media Library, module loader) - almost every business feature
(HRM, CRM, Accounting, POS, Support Ticket, ...) ships as a separate,
independently versioned Composer package under the
[`zerp-pk`](https://github.com/orgs/zerp-pk/repositories) GitHub org.
Expand Down Expand Up @@ -40,7 +40,7 @@ directory (commonly checked out as `ZerpPackages/` next to `zerp/`):

Composer symlinks each package into `vendor/zerp/<module>`. In production,
the same `require` line instead resolves against the module's real tagged
release on Packagist/VCS no path repository needed.
release on Packagist/VCS - no path repository needed.

## Where things live

Expand Down
22 changes: 11 additions & 11 deletions docs-developer/20-architecture/media-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ title: The Media Library
sidebar_position: 4
---

Every file a business uploads in Zerp employee documents, contract
Every file a business uploads in Zerp - employee documents, contract
attachments, project files, lead/deal files, candidate resumes, support
ticket attachments, avatars goes through one shared system: the Media
ticket attachments, avatars - goes through one shared system: the Media
Library, backed by `App\Services\MediaAttachmentService`.

## Why one shared service
Expand All @@ -23,26 +23,26 @@ App\Services\MediaAttachmentService::ensureDirectory(...)
App\Services\MediaAttachmentService::deleteMedia(...)
```

- **`upload()`** stores an `UploadedFile`, creates a `Media` row, links it
- **`upload()`** - stores an `UploadedFile`, creates a `Media` row, links it
to a directory/collection/owner.
- **`resolveOrBackfill()`** idempotent link-or-create: given an existing
- **`resolveOrBackfill()`** - idempotent link-or-create: given an existing
path-based reference (from before a table had a `media_id` column), finds
or creates the matching `Media` row without duplicating it on repeat
runs. Used by the backfill command below.
- **`ensureDirectory()`** gets-or-creates a `MediaDirectory` (e.g. one
- **`ensureDirectory()`** - gets-or-creates a `MediaDirectory` (e.g. one
per module/collection) to keep the library organized.
- **`deleteMedia()`** deletes both the `Media` row and the underlying
- **`deleteMedia()`** - deletes both the `Media` row and the underlying
file.

## Storage layout is deliberately flat

Files are stored flat `media/{file_name}`, occasionally with one extra
subpath segment (e.g. `employee_documents/xxx.pdf`) rather than using
Files are stored flat - `media/{file_name}`, occasionally with one extra
subpath segment (e.g. `employee_documents/xxx.pdf`) - rather than using
Spatie Media Library's default per-model/per-collection nested directory
structure. This is intentional: 35+ `MediaPicker` frontend consumers across
every module reconstruct file URLs assuming this flat shape. Spatie's real
`addMedia()` pipeline and conversions, and the registered `MediaPathGenerator`
class, exist in the dependency tree but are **not used** don't wire a new
class, exist in the dependency tree but are **not used** - don't wire a new
feature directly against Spatie's native API, go through
`MediaAttachmentService` instead so URL construction stays consistent.

Expand All @@ -55,7 +55,7 @@ paths) instead of a `media_id` foreign key:
2. Wire the controller's store/update/destroy to call
`MediaAttachmentService::upload()` / `deleteMedia()`.
3. Backfill existing rows with `php artisan media:backfill <source>` (see
`app/Console/Commands/BackfillMediaAttachments.php`) it's idempotent
`app/Console/Commands/BackfillMediaAttachments.php`) - it's idempotent
and supports `--dry-run`, and has a declarative `sources()` map you add
an entry to (path-column sources) or `jsonArraySources()` (for a column
storing a JSON array of paths, e.g. support-ticket attachments).
Expand All @@ -64,7 +64,7 @@ paths) instead of a `media_id` foreign key:

At least one pre-existing module (support-ticket) casts an `attachments`
column as `'array'` in Eloquent but its controllers were calling
`json_encode()` on the value before passing it to `create()`/`update()`
`json_encode()` on the value before passing it to `create()`/`update()`,
double-encoding it. If you add code that reads that column back through the
model's cast accessor, check for this rather than assuming the column is
clean JSON; linking through `MediaAttachmentService` from the pre-encode
Expand Down
16 changes: 8 additions & 8 deletions docs-developer/20-architecture/module-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ ZerpPackages/product-service/
└── ...
```

`composer.json` standard Composer package metadata, PSR-4 autoload, and a
`composer.json` - standard Composer package metadata, PSR-4 autoload, and a
Laravel `extra.laravel.providers` entry so the core app auto-discovers the
service provider:

Expand All @@ -41,7 +41,7 @@ service provider:
}
```

`module.json` metadata the core app reads at seed/runtime (not a Laravel
`module.json` - metadata the core app reads at seed/runtime (not a Laravel
convention, a Zerp-specific file `App\Classes\Module` and `PackageSeeder`
look for):

Expand All @@ -64,7 +64,7 @@ look for):
the legacy in-repo `packages/local/*` path) for a `module.json` in each
directory, and for every one it finds:

1. Creates an `AddOn` row (module, price, priority) if one doesn't exist
1. Creates an `AddOn` row (module, price, priority) if one doesn't exist,
`is_enable` is set based on whether the module was selected during
`app:install` (see below); every discovered module gets a row either
way, enabled or not.
Expand All @@ -74,12 +74,12 @@ directory, and for every one it finds:

This runs automatically as part of `php artisan app:install`, which
prompts for which modules to enable (a preset bundle or a custom
selection) see [Getting Started](../10-getting-started/getting-started.md).
selection) - see [Getting Started](../10-getting-started/getting-started.md).

## Cross-module integration: events, not direct calls

Modules never call into each other directly. The core app fires domain
events (`app/Events/*.php` `CreatePurchaseInvoice`, `CreateSalesInvoice`,
events (`app/Events/*.php` - `CreatePurchaseInvoice`, `CreateSalesInvoice`,
`CreateUser`, `CreateWarehouse`, etc.) and modules that care register a
listener via their own `EventServiceProvider`:

Expand All @@ -105,7 +105,7 @@ class PostPurchaseInvoiceListener

This is how `product-service` adjusts warehouse stock when an invoice is
posted, how `calendar`/`slack`/`telegram` react to the same invoice events
to create calendar entries or send notifications, and so on check
to create calendar entries or send notifications, and so on - check
`app/Events/` in the core app for the full list of events you can listen
for before adding a new one.

Expand All @@ -117,7 +117,7 @@ for roles/permissions, plus a plan/module-enable layer on top:
- `AddOn` + `UserActiveModule` (seeded by `PackageSeeder`, see above)
determine whether a module is enabled for a given company at all.
- `App\Classes\Module::isEnabled($name)` / `allEnabled()` check this at
runtime gate any module-specific route/controller/view behind it if the
runtime - gate any module-specific route/controller/view behind it if the
module can be toggled off.
- Regular Spatie roles/permissions (`client`, `staff`, custom roles) then
control what an individual *user* can do within an enabled module.
Expand All @@ -126,7 +126,7 @@ for roles/permissions, plus a plan/module-enable layer on top:

Once a module works locally against its path repository, cut a tagged
release so the core app (and anyone else) can depend on a real version
instead of a local symlink see
instead of a local symlink - see
[Publishing a Module Package](../30-guides/publishing-a-package.md).

Every class in every module is catalogued in the
Expand Down
6 changes: 3 additions & 3 deletions docs-developer/30-guides/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ title: Contributing
sidebar_position: 7
---

Zerp is a multi-repo project the core app and each module are separate
Zerp is a multi-repo project - the core app and each module are separate
repositories under [zerp-pk](https://github.com/orgs/zerp-pk/repositories).
Full contribution guidelines, including which repo a given change belongs
in, live in the core app:

- [CONTRIBUTING.md](https://github.com/zerp-pk/zerp/blob/main/CONTRIBUTING.md)
- [SECURITY.md](https://github.com/zerp-pk/zerp/blob/main/SECURITY.md) how to report a vulnerability
- [SECURITY.md](https://github.com/zerp-pk/zerp/blob/main/SECURITY.md) - how to report a vulnerability
- [CODE_OF_CONDUCT.md](https://github.com/zerp-pk/zerp/blob/main/CODE_OF_CONDUCT.md)

Quick pointers specific to working on the codebase:
Expand All @@ -23,5 +23,5 @@ Quick pointers specific to working on the codebase:
changing how modules integrate.

This docs site itself ([zerp-pk/docs](https://github.com/zerp-pk/docs)) is
also open to PRs fixes to unclear wording, missing steps, or outdated
also open to PRs - fixes to unclear wording, missing steps, or outdated
screenshots are always welcome.
10 changes: 5 additions & 5 deletions docs-developer/30-guides/docker-and-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ build:
```

This is required because `vendor/zerp/*` is a set of symlinks pointing at
the sibling `ZerpPackages/<module>` directories if the build context were
the sibling `ZerpPackages/<module>` directories - if the build context were
just `zerp/`, those directories wouldn't be visible to Docker at all and
every module symlink would point at nothing. Every build stage that runs
`composer install` or otherwise touches `vendor/zerp/*` also does
`COPY ZerpPackages /ZerpPackages` first.

`docker-compose.yml` runs three services: `app` (the built image, port
`8000:80`), `db` (`mysql:8`), `redis` (`redis:7`) `.env`'s `DB_HOST`/
`8000:80`), `db` (`mysql:8`), `redis` (`redis:7`) - `.env`'s `DB_HOST`/
`REDIS_HOST` are overridden to the service names (`db`/`redis`) so the app
container can reach them.

Expand All @@ -46,7 +46,7 @@ to choose modules non-interactively instead.

`.github/workflows/ci.yml` runs on every push/PR to `main`. It's
deliberately narrow: `composer validate`, a PHP lint pass over `app/`, and
an `npm run build` of the **core app only** it does not run
an `npm run build` of the **core app only** - it does not run
`composer install`, PHPUnit, or anything that needs a module package
present.

Expand All @@ -58,8 +58,8 @@ locally (see [Getting Started](../10-getting-started/getting-started.md)).

Two smaller things worth knowing if you touch the workflow:

- No `cache: "npm"` on `actions/setup-node` that requires a committed
- No `cache: "npm"` on `actions/setup-node` - that requires a committed
`package-lock.json`, which this repo doesn't commit.
- `npm install`, not `npm ci` same reason, `npm ci` requires an existing
- `npm install`, not `npm ci` - same reason, `npm ci` requires an existing
lock file.

4 changes: 2 additions & 2 deletions docs-developer/30-guides/publishing-a-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ other installs can depend on a real version.
## Version tags are immutable

**Never re-tag a version that's already been published.** If `v1.0.1` is
already out and you need to fix something in it, cut `v1.0.2` don't
already out and you need to fix something in it, cut `v1.0.2` - don't
force-push over the existing tag. Anyone who already resolved `v1.0.1` (a
lockfile, a deployed environment) would silently get different code under
the same version number, which is exactly the class of bug semantic
Expand All @@ -26,7 +26,7 @@ git push origin v1.0.2
are correct and `module.json` metadata (`name`, `alias`, `package_name`,
pricing) matches what you want the core app to seed.
2. Commit and push to the module's own repo (`zerp-pk/<module>`).
3. Tag a new version (patch/minor/major per semver a bug fix is a patch,
3. Tag a new version (patch/minor/major per semver - a bug fix is a patch,
a new feature is a minor, a breaking change to the module's public
API/events is a major).
4. If the core app's `composer.json` should track the new version instead
Expand Down
Loading
Loading