# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
php bin/console                                  # list all commands
php bin/console doctrine:migrations:diff         # generate migration from entity changes
php bin/console doctrine:migrations:migrate      # run pending migrations
php bin/console cache:clear                      # clear Symfony cache
php bin/console cronjob:<name>                   # run a cronjob manually
```

## Architecture

**Symfony 7.1, PHP 8.2+, PostgreSQL**

The system is a B2B order management platform (TshirtOrder) with two bundles:
- `ApplicationApiBundle` — REST API consumed by the React frontend (`/api/v1/`)
- `ApplicationWebshopBundle` — server-side rendered webshop (`/ecom/{channelEcomId}/`)

### Request flow (API)

```
Frontend → POST /api/v1/channels/ (Bearer token)
→ ChannelController::add()
→ ChannelService::add($data)
→ EntityManager persist/flush
```

Auth is handled in every controller by calling `ApiService::getToken($authorization)`. Returns `null` on failure → controller returns 401.

Services always return `['data' => ..., 'status_code' => int]`.

### Directory structure

| Path | Purpose |
|------|---------|
| `src/Entity/` | Doctrine ORM entities → PostgreSQL. IDs use `SEQUENCE` strategy. |
| `src/Repository/` | Custom query methods via `->query([...])` pattern |
| `src/Service/` | All business logic. Each entity has a matching Service. |
| `src/Application/ApiBundle/Controller/` | Thin controllers — delegate everything to Services |
| `src/Application/ApiBundle/Resources/config/route/` | One YAML file per resource, included in `routing.yaml` |
| `src/Command/Cronjob/` | Scheduled jobs (`#[AsCommand(name: 'cronjob:...')]`) |
| `src/Command/Update/` | One-off data update scripts |
| `migrations/` | Doctrine migrations (auto-generated, never hand-edit) |

### Key entities

- **Channel** (`src/Entity/Channel.php`) — central config entity. Each channel has its own settings for invoicing, ecom, Shopify, prepaid, Svea, etc. All product/order data belongs to a channel.
- **Product** (`src/Entity/Product.php`) — has `sku`, `idWp` (WooCommerce ID), `stock` fields. Belongs to many channels.
- **Order / OrderProduct** — order headers and line items.

### Service conventions

Each service has:
- `generateItem(Entity $entity): array` — maps entity to the array format returned to frontend
- `list($data)`, `find($id)`, `add($data)`, `update($id, $data)`, `delete($id)` — standard CRUD

### Cronjob pattern

Cronjobs are Symfony Console Commands in `src/Command/Cronjob/`. They query channels with specific flags enabled, then call a service method per channel. Logging goes to a flat file (e.g. `shopify_logs/clone_product_stock_{date}.txt`). See `ShopifyCloneProductStockCommand` as the reference implementation.

### Adding new Channel settings

1. Add column(s) to `src/Entity/Channel.php` with getter/setter
2. Run `doctrine:migrations:diff` → `migrate`
3. Add the field to `ChannelService::generateItem()` (read) and `ChannelService::update()` (write)

### Integrations

| Integration | Where |
|------------|-------|
| WooCommerce / WordPress | `synId` on Channel, `idWp` on Product; existing stock sync in `ShopifyCloneProductStockCommand` |
| Shopify | `src/Service/ShopifyService.php`, Channel fields `shopifyApiKey/Url/AccessToken` |
| FTP | `phpseclib/phpseclib` library; FTP params in `config/services.yaml` under `garp_ftp_*` |
| PostNord | `src/Service/PostNordService.php` |
| Svea (checkout) | `src/Service/Payment/SveaService.php`, Channel fields `svea*` |
| Swish | `src/Service/Payment/SwishService.php` |
| Mailchimp Transactional | `src/Service/MailService.php` |

### FTP params (services.yaml)

```yaml
garp_ftp_host: 195.67.131.74
garp_ftp_username: garpout
garp_ftp_password: ...
garp_ftp_local_csv_path: public/uploads/garp_csv
```

For WOO stock export FTP, new params will be added here and injected via `ParameterBagInterface`.

## Issue docs

Whenever asked to create an issue doc (e.g. "tạo doc issue", "TSHIRTORDER-XXXX ... tạo file doc"), always follow `docs/issues/_TEMPLATE.md` — read it first, then follow its structure and its own instructions on flat file vs. folder-with-`files/` layout (folder when mockups/images are involved). Before writing the TODO list, check whether the relevant entity/service/migration already exists in the codebase — several past tickets turned out to be already implemented on the backend.