diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 16479cd..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 86e6554..2caf24f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -15,7 +15,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest] - php: [8.2, 8.3, 8.4] + php: [8.2, 8.3, 8.4, 8.5] name: PHP ${{ matrix.php }} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..113cd9f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,342 @@ +# Waybill — AI Agent Guide + +This document helps AI agents understand the structure and architecture of the `cable8mm/waybill` package for accurate code generation and modification. + +## Project Overview + +A PHP library that generates PDF waybills for Korean courier services (CJ Logistics). Built with Factory + Enum patterns for easy extension to new parcel services. + +- **Namespace root**: `Cable8mm\Waybill` +- **PHP version**: ^8.2 +- **PDF engine**: mpdf/mpdf ^8.0 +- **Template engine**: cable8mm/stub-template (Twig-based) +- **Faker**: fakerphp/faker + mbezhanov/faker-provider-collection +- **Barcode**: picqer/php-barcode-generator + +## Directory Structure + +``` +waybill/ +├── src/ +│ ├── Waybill.php # Main entry: create single waybill +│ ├── WaybillCollection.php # Manage multiple waybills +│ ├── Slicer.php # Extract a single waybill from a PDF page +│ ├── Collections/ +│ │ └── Waybills.php # Waybill object container (ArrayAccess, Countable, IteratorAggregate) +│ ├── Enums/ +│ │ └── ParcelService.php # Parcel service enum (core extension point) +│ ├── Factories/ +│ │ ├── Factory.php # Abstract factory (definition + state + create pattern) +│ │ └── CjFactory.php # CJ Logistics implementation +│ ├── Support/ +│ │ ├── Faker.php # Faker wrapper for Korean locale data +│ │ └── Mpdf.php # mPDF instance factory (includes Korean font config) +│ └── ... +├── stubs/ +│ └── Cj.stub # CJ waybill Twig template +├── fonts/ +│ └── NanumBarunGothic.ttf # Korean font +├── config.php # mPDF settings (font, paper, margins, watermark) +└── tests/ +``` + +## Architecture & Key Patterns + +### 1. Static Factory Method (`of()` / `make()`) + +All major classes use static factory methods: + +```php +// of() — auto-creates all dependencies +Waybill::of(ParcelService::Cj); + +// make() — create instance without mpdf, inject later +Waybill::make(ParcelService::Cj)->mpdf($mpdf); +``` + +### 2. Fluent API (Method Chaining) + +All setters return `$this;` to support chaining: + +```php +Waybill::of(ParcelService::Cj) + ->state(['seller' => ['name' => '회사명']]) + ->path('/dist') + ->save('waybill.pdf'); +``` + +### 3. Enum + Factory Extension Pattern + +To add a new parcel service, modify 3 files: + +| Step | File | Description | +| ---- | --------------------------------- | ---------------------------------------------- | +| 1 | `src/Factories/{Name}Factory.php` | Extend `Factory`, implement `definition()` | +| 2 | `src/Enums/ParcelService.php` | Add `case Name = 'value'`, implement 3 methods | +| 3 | `stubs/{Name}.stub` | Create Twig template | + +### 4. State Pattern + +`Factory::state()` overrides specific fields from `definition()`: + +```php +// Factory.php +public function create(): array +{ + $record = $this->definition(); + if (! empty($this->state)) { + $record = array_replace($record, $this->state); + } + return $record; +} +``` + +Both `Waybill::toArray()` and `Waybill::write()` internally call `Factory::create()`. + +### 5. mPDF Instances Must Be Created via Support\Mpdf Only + +```php +// CORRECT — Korean font config is applied +use Cable8mm\Waybill\Support\Mpdf; +$mpdf = Mpdf::instance(); + +// WRONG — bypasses config.php settings (no Korean font support) +$mpdf = new \Mpdf\Mpdf(); // ❌ +``` + +## Class Details + +### Waybill (`src/Waybill.php`) + +| Method | Description | +| -------------------------- | ----------------------------------- | +| `of(ParcelService, ?Mpdf)` | Static factory (auto-creates Mpdf) | +| `make(ParcelService)` | Create instance without Mpdf | +| `mpdf(Mpdf)` | Inject Mpdf instance | +| `state(array)` | Set custom data | +| `path(string)` | Set save path | +| `toArray(): array` | Return data as array | +| `save(string): mixed` | Save as PDF file | +| `download(string): mixed` | Output PDF as download stream | +| `write(?array)` | Render HTML and write to Mpdf | +| `__toString(): string` | Return service name (e.g. "CJ택배") | + +### WaybillCollection (`src/WaybillCollection.php`) + +| Method | Description | +| --------------------------- | -------------------------------- | +| `of(?Waybills, int, ?Mpdf)` | Static factory | +| `make(?Waybills, int)` | Create without Mpdf | +| `add(Waybill\|array)` | Add waybill(s) | +| `path(string)` | Set save path | +| `toArray(): array` | Return all waybill data as array | +| `save(string): mixed` | Save as PDF | +| `download(string): mixed` | Download as PDF | + +### Slicer (`src/Slicer.php`) + +| Method | Description | +| ---------------------------------- | --------------------------------- | +| `of(ParcelService, int)` | Static factory (with page number) | +| `source(string)` | Set source PDF path | +| `page(int)` | Set page number to extract | +| `save(string, Destination): mixed` | Save page | +| `download(string): mixed` | Download page | + +### ParcelService Enum (`src/Enums/ParcelService.php`) + +Methods to implement when adding a new service: + +```php +enum ParcelService: string +{ + case Cj = 'CJ택배'; + + // Returns full namespace of the Factory class + public function factoryClass(): string; + + // Returns the stub file path + public function stub(): string; + + // Returns crop region [offsetX, offsetY, width, height] for Slicer + public function templateArea(): array; +} +``` + +### Factory Abstract Class (`src/Factories/Factory.php`) + +```php +abstract class Factory +{ + abstract public function definition(): array; + public static function make(?array $state = []): static; + public function state(array $state): static; + public function create(): array; // definition() + state merge +} +``` + +### CjFactory (`src/Factories/CjFactory.php`) + +Fields returned by `definition()`: + +| Key | Type | Description | +| --------------------- | ----------------------------------------- | -------------------- | +| `city` | `array{code, name}` | City code/name | +| `region` | `array{code, name}` | Region code/name | +| `line_items` | `string[]` | Product list | +| `seller` | `array{name, phone, site, address}` | Seller info | +| `receiver` | `array{name, phone, cell_phone, address}` | Receiver info | +| `printed` | `string (Y-m-d)` | Print date | +| `total_printed_count` | `int` | Total print count | +| `site_order_no` | `string` | Order number | +| `tracking_number` | `string` | Tracking number | +| `delivery_worker` | `string` | Delivery worker name | +| `settlement_type` | `string` | Settlement type | +| `print_date` | `string (Y-m-d)` | Print date | +| `box_quantity` | `int` | Box quantity | +| `freight_type` | `string` | Freight type | +| `barcode` | `string (base64 img)` | Barcode image | + +### Mpdf Factory (`src/Support/Mpdf.php`) + +- Reads all settings from `config.php` to create mPDF instance +- Automatically registers Korean font (NanumBarunGothic) +- Applies watermark, font, paper settings + +### Faker (`src/Support/Faker.php`) + +- Generates Korean data (names, addresses, phone numbers, company names) +- Generates barcodes via `picqer/php-barcode-generator` +- Generates product names via `mbezhanov/faker-provider-collection` + +## Adding a New Parcel Service (Step-by-Step) + +Example: Adding "UPS" + +### 1. Create factory class + +```php +// src/Factories/UpsFactory.php +namespace Cable8mm\Waybill\Factories; + +use Cable8mm\Waybill\Support\Faker; + +class UpsFactory extends Factory +{ + public function definition(): array + { + return [ + // Fields tailored to UPS + 'sender' => [ + 'name' => Faker::shared()->company(), + 'phone' => Faker::shared()->phoneNumber(), + 'address' => Faker::shared()->address(), + ], + 'receiver' => [ + 'name' => Faker::shared()->name(), + 'phone' => Faker::shared()->phoneNumber(), + 'address' => Faker::shared()->address(), + ], + 'tracking_number' => Faker::shared()->bothify('1Z-????-????-????'), + // ... + ]; + } +} +``` + +### 2. Add enum case + +```php +// src/Enums/ParcelService.php +enum ParcelService: string +{ + case Cj = 'CJ택배'; + case Ups = 'UPS'; // ADD + + public function factoryClass(): string + { + return match ($this) { + self::Cj => \Cable8mm\Waybill\Factories\CjFactory::class, + self::Ups => \Cable8mm\Waybill\Factories\UpsFactory::class, // ADD + }; + } + + public function stub(): string + { + $stubPath = match ($this) { + self::Cj => realpath(...), + self::Ups => realpath(...), // ADD + }; + // ... + } + + public function templateArea(): array + { + return match ($this) { + self::Cj => [-40, -46, 285, 196], + self::Ups => [0, 0, 210, 297], // ADD (A4 size) + }; + } +} +``` + +### 3. Create stub template + +```twig +{# stubs/Ups.stub #} + +UPS Waybill + +

{{ sender.name }}

+

{{ receiver.name }} - {{ tracking_number }}

+ + +``` + +## Testing Conventions + +- Test classes must be `final class` +- Test method names use snake_case (e.g. `test_it_create_with_state`) +- PDF generation tests must `unlink()` files after assertion +- Output tests must use `ob_start()` / `ob_end_clean()` +- Reflection for private property access requires `setAccessible(true)` + +```php +final class WaybillTest extends TestCase +{ + public function test_path_exists(): void + { + $reflection = new ReflectionClass($waybill); + $path = $reflection->getProperty('path'); + $path->setAccessible(true); + $this->assertStringContainsString('dist', $path->getValue($waybill)); + } +} +``` + +## Common Pitfalls + +1. **NEVER use `array_values()` in `Factory::create()`** — This destroys associative array keys and breaks state merging. Use `array_replace()` instead. +2. **NEVER call `definition()` in `Waybill::write()`** — State won't be applied. Always use `create()` instead. +3. **NEVER instantiate `new \Mpdf\Mpdf()` directly in `Slicer`** — Always use `Support\Mpdf::instance()` to get Korean font config. +4. **ALWAYS set default value for `$path` in `Waybill`/`WaybillCollection`** — `private string $path = ''` is required to avoid uninitialized property warnings. + +## Composer Scripts + +| Script | Command | +| ------------------------ | ----------------------------- | +| `composer test` | `vendor/bin/phpunit` | +| `composer test-coverage` | Generate HTML coverage report | +| `composer lint` | Laravel Pint code style check | +| `composer apidoc` | Doctum API documentation | + +## CI/CD + +GitHub Actions workflows: + +- `.github/workflows/code-style.yml` — Laravel Pint check +- `.github/workflows/run-tests.yml` — PHPUnit test suite + +## License + +MIT diff --git a/README.md b/README.md index f617d6e..a78af32 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,21 @@ [![Total Downloads](https://img.shields.io/packagist/dt/cable8mm/waybill.svg)](https://packagist.org/packages/cable8mm/waybill) [![Packagist Stars](https://img.shields.io/packagist/stars/cable8mm/waybill)](https://github.com/cable8mm/waybill/stargazers) -A lightweight PHP library for generating PDF waybills with ease. This package allows developers to create and customize waybills in PDF format for courier and logistics services. It supports barcode generation, sender/receiver details, and customizable layouts. Perfect for automating shipping label creation in your e-commerce or logistics applications. +PHP로 PDF 운송장을 손쉽게 생성할 수 있는 라이브러리입니다. CJ대한통운을 기본으로 지원하며, 바코드 생성, 송신자/수신자 정보, 커스터마이징 가능한 레이아웃을 제공합니다. 쇼핑몰이나 물류 시스템에서 배송 레이블 생성을 자동화하는 데 적합합니다. -## Installation +## 설치 -You can install the package via composer: +Composer로 설치할 수 있습니다: ```bash composer require cable8mm/waybill ``` -## Usage +## 사용법 -Save a waybill for pdf format: +### 빠른 시작 + +기본 운송장을 PDF로 저장: ```php use Cable8mm\Waybill\Enums\ParcelService; @@ -30,16 +32,61 @@ Waybill::of(ParcelService::Cj) ->save('test.pdf'); ``` -Get a waybill array: +운송장 데이터를 배열로 가져오기 (API 응답 또는 CSV 내보내기용): ```php +use Cable8mm\Waybill\Enums\ParcelService; +use Cable8mm\Waybill\Waybill; + $waybill = Waybill::of(ParcelService::Cj) - ->toArray() + ->toArray(); +``` + +### 커스텀 데이터 사용하기 (`state()`) + +실제 주문 데이터로 특정 필드를 덮어씁니다: + +```php +use Cable8mm\Waybill\Enums\ParcelService; +use Cable8mm\Waybill\Waybill; + +Waybill::of(ParcelService::Cj) + ->state([ + 'seller' => ['name' => '내회사', 'phone' => '02-1234-5678'], + 'receiver' => ['name' => '홍길동', 'phone' => '010-1234-5678'], + 'tracking_number' => 'CJ-1234-5678-9012', + ]) + ->path(realpath(__DIR__.'/../dist')) + ->save('waybill.pdf'); +``` + +### 인스턴스 분리 생성 후 mpdf 주입 + +`Waybill` 인스턴스를 먼저 만들고, 나중에 `mpdf`를 주입합니다: + +```php +use Cable8mm\Waybill\Enums\ParcelService; +use Cable8mm\Waybill\Support\Mpdf; +use Cable8mm\Waybill\Waybill; + +$mpdf = Mpdf::instance(); + +$waybill = Waybill::make(ParcelService::Cj) + ->mpdf($mpdf) + ->path(realpath(__DIR__.'/../dist')) + ->save('test.pdf'); ``` -Save multiple waybills for pdf format: +### 여러 개의 운송장 생성 + +하나의 PDF 파일에 여러 장의 운송장을 저장: ```php +use Cable8mm\Waybill\Enums\ParcelService; +use Cable8mm\Waybill\Support\Mpdf; +use Cable8mm\Waybill\Waybill; +use Cable8mm\Waybill\WaybillCollection; + $mpdf = Mpdf::instance(); WaybillCollection::of(mpdf: $mpdf) @@ -48,56 +95,81 @@ WaybillCollection::of(mpdf: $mpdf) ->path(realpath(__DIR__.'/../dist')) ->save('collection.pdf'); -// or - +// 또는 한 번에 여러 개 추가: WaybillCollection::of(mpdf: $mpdf) ->add([ Waybill::of(ParcelService::Cj, mpdf: $mpdf), - Waybill::of(ParcelService::Cj, mpdf: $mpdf), + Waybill::of(ParcelService::Cj, mpdf: $mpdf), ]) ->path(realpath(__DIR__.'/../dist')) ->save('collection.pdf'); ``` -Slice the page of the waybills: +### PDF에서 특정 운송장만 추출하기 + +여러 페이지로 된 PDF에서 원하는 페이지 하나만 잘라냅니다: ```php +use Cable8mm\Waybill\Enums\ParcelService; +use Cable8mm\Waybill\Slicer; + Slicer::of(ParcelService::Cj, 1) ->source('source.pdf') - ->save('one_page.pdf'); // or `->download('one_page.pdf')` + ->save('one_page.pdf'); // 또는 ->download('one_page.pdf') 로 다운로드 +``` + +### 설정 + +`config.php` 파일에서 mPDF 설정을 관리합니다. 한글 폰트(NanumBarunGothic), 용지 크기, 여백, 워터마크 등을 설정할 수 있습니다: + +```php +return [ + 'mode' => 'utf-8', + 'format' => 'A4', + 'custom_font_dir' => __DIR__.'/fonts', + 'custom_font_data' => [ + 'nbg' => [ + 'R' => 'NanumBarunGothic.ttf', + ], + ], + 'default_font' => 'nbg', + // ... +]; ``` -### How to customize +### 택배사 추가하기 (커스터마이징) -If you want to add another parcel service like UPS, you would need to make `Enums` and `Factory` class, for example: +UPS 같은 새 택배사를 추가하려면 `Enum`과 `Factory` 클래스를 만들어야 합니다: -1. Make `UpsFactory.php` into `src/Factories' folder. -2. Make `Enum` element into `src/Enums` folder. +1. `src/Factories/` 폴더에 `UpsFactory.php` 생성 +2. `src/Enums/ParcelService.php` Enum에 `Ups` 케이스 추가 +3. `factoryClass()`, `stub()`, `templateArea()` 메서드 구현 +4. PDF 레이아웃을 위한 `stubs/Ups.stub` 파일 생성 -### Testing +### 테스트 ```bash composer test ``` -### Changelog +### 변경 내역 -Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. +자세한 내용은 [CHANGELOG](CHANGELOG.md)를 확인해주세요. -## Contributing +## 기여하기 -Please see [CONTRIBUTING](CONTRIBUTING.md) for details. +기여 방법은 [CONTRIBUTING](CONTRIBUTING.md)을 참고해주세요. -### Security +### 보안 -If you discover any security related issues, please email instead of using the issue tracker. +보안 관련 이슈는 Issue Tracker 대신 으로 메일 부탁드립니다. -## Credits +## 크레딧 - [Samgu Lee](https://github.com/cable8mm) - [All Contributors](../../contributors) -## License +## 라이선스 -The MIT License (MIT). Please see [License File](LICENSE) for more information. +MIT 라이선스입니다. 자세한 내용은 [License File](LICENSE)을 확인해주세요. diff --git a/src/Collections/Waybills.php b/src/Collections/Waybills.php index 57ffba3..4c191fd 100644 --- a/src/Collections/Waybills.php +++ b/src/Collections/Waybills.php @@ -18,7 +18,7 @@ class Waybills implements ArrayAccess, Countable, IteratorAggregate */ public function __construct( /** - * @var array Array of Waybill objects + * @var array Array of Waybill objects */ private array $container = [] ) { @@ -28,7 +28,7 @@ public function __construct( /** * Adds a Waybill object to the container * - * @param \Cable8mm\Waybill\Waybill|array $waybill a Waybill object + * @param Waybill|array $waybill a Waybill object * @return static The method returns the instance */ public function add(Waybill|array $waybill): static diff --git a/src/Enums/ParcelService.php b/src/Enums/ParcelService.php index 7929077..127363f 100644 --- a/src/Enums/ParcelService.php +++ b/src/Enums/ParcelService.php @@ -2,6 +2,8 @@ namespace Cable8mm\Waybill\Enums; +use Cable8mm\Waybill\Factories\CjFactory; + enum ParcelService: string { /** @@ -19,7 +21,7 @@ enum ParcelService: string public function factoryClass(): string { return match ($this) { - self::Cj => \Cable8mm\Waybill\Factories\CjFactory::class, + self::Cj => CjFactory::class, }; } @@ -44,13 +46,18 @@ public function stub(): string } /** - * Get area variables + * Get the template area for slicing a specific waybill from a page + * + * Returns [offsetX, offsetY, width, height] in mm units. + * These values define the crop region when extracting a single waybill + * from a multi-waybill PDF page using mPDF's ImportPage + UseTemplate. * - * @return array[int,int,int,int] The area variables + * @return array{0: int, 1: int, 2: int, 3: int} The area as [offsetX, offsetY, width, height] */ public function templateArea(): array { return match ($this) { + // CJ waybill: offset (-40mm, -46mm), size 285mm x 196mm self::Cj => [-40, -46, 285, 196], }; } diff --git a/src/Factories/Factory.php b/src/Factories/Factory.php index 689505d..ee9f49f 100644 --- a/src/Factories/Factory.php +++ b/src/Factories/Factory.php @@ -7,7 +7,7 @@ abstract class Factory /** * Change key-value pairs in the definition */ - private array $state; + private array $state = []; /** * Define a factory definition for online mall companies @@ -47,12 +47,10 @@ public function state(array $state): static */ public function create(): array { - $record = array_values($this->definition()); + $record = $this->definition(); if (! empty($this->state)) { - foreach ($this->state as $key => $value) { - $record[$key] = $this->{$key} ?? $value; - } + $record = array_replace($record, $this->state); } return $record; diff --git a/src/Slicer.php b/src/Slicer.php index e20bd05..394ec4d 100644 --- a/src/Slicer.php +++ b/src/Slicer.php @@ -63,7 +63,7 @@ public function page(int $page): static * Save the page of waybills * * @param string $path The path to save the waybills - * @param \Mpdf\Output\Destination $destination The destination + * @param Destination $destination The destination * @return mixed The method returns */ public function save(string $path, $destination = Destination::FILE): mixed @@ -105,7 +105,7 @@ public function download(string $path): mixed * @param int $page The page to save the waybills * @return static The method returns the Slicer instance * - * @example Slicer::of(ParcelService::Cj)->... + * @example Slicer::of(ParcelService::Cj, 1)->... */ public static function of(ParcelService $parcelService, int $page): static { diff --git a/src/Support/Faker.php b/src/Support/Faker.php index 1f79cd4..60b68a0 100644 --- a/src/Support/Faker.php +++ b/src/Support/Faker.php @@ -2,10 +2,17 @@ namespace Cable8mm\Waybill\Support; +use Bezhanov\Faker\Provider\Commerce; +use Bezhanov\Faker\Provider\Device; +use Faker\Factory; +use Faker\Generator; +use Picqer\Barcode\Renderers\PngRenderer; +use Picqer\Barcode\Types\TypeCode128; + class Faker { /** - * @var \Faker\Generator + * @var Generator */ private static $instance; @@ -13,15 +20,15 @@ class Faker * Get \Faker\Generator singleton instance * * @param ?string $locale the locale - * @return \Faker\Generator The method returns \Faker\Generator singleton instance + * @return Generator The method returns \Faker\Generator singleton instance */ - public static function shared(?string $locale = 'ko_KR'): \Faker\Generator + public static function shared(?string $locale = 'ko_KR'): Generator { if (! isset(self::$instance)) { - self::$instance = \Faker\Factory::create($locale); + self::$instance = Factory::create($locale); - self::$instance->addProvider(new \Bezhanov\Faker\Provider\Commerce(self::$instance)); - self::$instance->addProvider(new \Bezhanov\Faker\Provider\Device(self::$instance)); + self::$instance->addProvider(new Commerce(self::$instance)); + self::$instance->addProvider(new Device(self::$instance)); } return self::$instance; @@ -50,9 +57,9 @@ public function site(): string */ public function barcode(): string { - $barcode = (new \Picqer\Barcode\Types\TypeCode128)->getBarcode(self::shared()->ean13()); + $barcode = (new TypeCode128)->getBarcode(self::shared()->ean13()); - $renderer = new \Picqer\Barcode\Renderers\PngRenderer; + $renderer = new PngRenderer; return 'data:image/png;base64,'.base64_encode($renderer->render($barcode)); } diff --git a/src/Support/Mpdf.php b/src/Support/Mpdf.php index fe54e37..d16e0ef 100644 --- a/src/Support/Mpdf.php +++ b/src/Support/Mpdf.php @@ -2,17 +2,20 @@ namespace Cable8mm\Waybill\Support; +use Mpdf\Config\ConfigVariables; +use Mpdf\Config\FontVariables; + class Mpdf { public static function instance(): \Mpdf\Mpdf { $config = include __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'config.php'; - $defaultConfig = (new \Mpdf\Config\ConfigVariables)->getDefaults(); + $defaultConfig = (new ConfigVariables)->getDefaults(); $fontDirs = $defaultConfig['fontDir']; $tempDir = $defaultConfig['tempDir']; - $defaultFontConfig = (new \Mpdf\Config\FontVariables)->getDefaults(); + $defaultFontConfig = (new FontVariables)->getDefaults(); $fontData = $defaultFontConfig['fontdata']; $configGlobal = [ diff --git a/src/Waybill.php b/src/Waybill.php index 3eca224..e8eea60 100644 --- a/src/Waybill.php +++ b/src/Waybill.php @@ -20,7 +20,7 @@ class Waybill /** * The path to save the waybills */ - private string $path; + private string $path = ''; /** * Constructor @@ -50,9 +50,9 @@ public function write(?array $args = null): void if (! is_null($args)) { $data = $args; } elseif (! empty($this->state)) { - $data = $this->parcelService->factoryClass()::make()->state($this->state)->definition(); + $data = $this->parcelService->factoryClass()::make()->state($this->state)->create(); } else { - $data = $this->parcelService->factoryClass()::make()->definition(); + $data = $this->parcelService->factoryClass()::make()->create(); } $this->mpdf->WriteHTML( diff --git a/src/WaybillCollection.php b/src/WaybillCollection.php index b251e63..dea3ca8 100644 --- a/src/WaybillCollection.php +++ b/src/WaybillCollection.php @@ -14,7 +14,7 @@ class WaybillCollection /** * @var string The path to save the waybills */ - private string $path; + private string $path = ''; /** * Constructor @@ -39,7 +39,7 @@ private function __construct( /** * Adds a Waybill object to the container * - * @param \Cable8mm\Waybill\Waybill|array $waybill a Waybill object + * @param Waybill|array $waybill a Waybill object * @return static The method returns the instance */ public function add(Waybill|array $waybill): static diff --git a/tests/Enums/ParcelServiceTest.php b/tests/Enums/ParcelServiceTest.php index caa57ec..b438e16 100644 --- a/tests/Enums/ParcelServiceTest.php +++ b/tests/Enums/ParcelServiceTest.php @@ -3,6 +3,7 @@ namespace Cable8mm\Waybill\Tests\Enums; use Cable8mm\Waybill\Enums\ParcelService; +use Cable8mm\Waybill\Factories\CjFactory; use PHPUnit\Framework\TestCase; final class ParcelServiceTest extends TestCase @@ -19,11 +20,22 @@ public function test_value(): void public function test_factory_class(): void { - $this->assertEquals(\Cable8mm\Waybill\Factories\CjFactory::class, ParcelService::Cj->factoryClass()); + $this->assertEquals(CjFactory::class, ParcelService::Cj->factoryClass()); } public function test_stub(): void { $this->assertIsString(ParcelService::Cj->stub()); } + + public function test_template_area(): void + { + $area = ParcelService::Cj->templateArea(); + + $this->assertCount(4, $area); + $this->assertIsInt($area[0]); + $this->assertIsInt($area[1]); + $this->assertIsInt($area[2]); + $this->assertIsInt($area[3]); + } } diff --git a/tests/Factories/CjFactoryTest.php b/tests/Factories/CjFactoryTest.php index 12a6408..c4760e4 100644 --- a/tests/Factories/CjFactoryTest.php +++ b/tests/Factories/CjFactoryTest.php @@ -28,4 +28,46 @@ public function test_it_create_with_state(): void $this->assertEquals('10293', $cjFactory['city']['code']); $this->assertEquals('new', $cjFactory['city']['name']); } + + public function test_it_create_with_empty_state_returns_original_definition(): void + { + $cjFactory = CjFactory::make()->state([])->create(); + + $this->assertArrayHasKey('city', $cjFactory); + $this->assertArrayHasKey('seller', $cjFactory); + $this->assertArrayHasKey('receiver', $cjFactory); + $this->assertArrayHasKey('barcode', $cjFactory); + } + + public function test_it_create_with_partial_state_override(): void + { + $cjFactory = CjFactory::make()->state(['tracking_number' => 'OVERRIDE-1234'])->create(); + + $this->assertEquals('OVERRIDE-1234', $cjFactory['tracking_number']); + // Other fields should remain from the original definition + $this->assertArrayHasKey('city', $cjFactory); + $this->assertArrayHasKey('seller', $cjFactory); + } + + public function test_it_create_without_state_returns_full_definition(): void + { + $cjFactory = CjFactory::make()->create(); + + $this->assertCount(15, $cjFactory); + $this->assertArrayHasKey('city', $cjFactory); + $this->assertArrayHasKey('region', $cjFactory); + $this->assertArrayHasKey('line_items', $cjFactory); + $this->assertArrayHasKey('seller', $cjFactory); + $this->assertArrayHasKey('receiver', $cjFactory); + $this->assertArrayHasKey('printed', $cjFactory); + $this->assertArrayHasKey('total_printed_count', $cjFactory); + $this->assertArrayHasKey('site_order_no', $cjFactory); + $this->assertArrayHasKey('tracking_number', $cjFactory); + $this->assertArrayHasKey('delivery_worker', $cjFactory); + $this->assertArrayHasKey('settlement_type', $cjFactory); + $this->assertArrayHasKey('print_date', $cjFactory); + $this->assertArrayHasKey('box_quantity', $cjFactory); + $this->assertArrayHasKey('freight_type', $cjFactory); + $this->assertArrayHasKey('barcode', $cjFactory); + } } diff --git a/tests/SlicerTest.php b/tests/SlicerTest.php index d3daed9..a4112e2 100644 --- a/tests/SlicerTest.php +++ b/tests/SlicerTest.php @@ -12,7 +12,7 @@ final class SlicerTest extends TestCase { - public function test_path_method(): void + public function test_source_method(): void { $slicer = Slicer::of(ParcelService::Cj, 1) ->source(realpath(__DIR__.'/../dist')); @@ -26,6 +26,20 @@ public function test_path_method(): void $this->assertStringContainsString(DIRECTORY_SEPARATOR.'dist', $source->getValue($slicer)); } + public function test_page_method(): void + { + $slicer = Slicer::of(ParcelService::Cj, 1) + ->page(3); + + $reflection = new ReflectionClass($slicer); + + $page = $reflection->getProperty('page'); + + $page->setAccessible(true); + + $this->assertEquals(3, $page->getValue($slicer)); + } + public function test_save_method(): void { $mpdf = Mpdf::instance(); diff --git a/tests/WaybillCollectionTest.php b/tests/WaybillCollectionTest.php index 50cdd67..b605932 100644 --- a/tests/WaybillCollectionTest.php +++ b/tests/WaybillCollectionTest.php @@ -100,4 +100,22 @@ public function test_save_with_chaining(): void unlink(realpath(__DIR__.'/../dist').DIRECTORY_SEPARATOR.'collection.pdf'); } + + public function test_download_method(): void + { + $mpdf = Mpdf::instance(); + + $waybillCollection = WaybillCollection::of(mpdf: $mpdf) + ->add(Waybill::of(ParcelService::Cj, mpdf: $mpdf)); + + ob_start(); + + $waybillCollection->download('collection_download.pdf'); + + $content = ob_get_contents(); + + ob_end_clean(); + + $this->assertNotEmpty($content); + } } diff --git a/tests/WaybillTest.php b/tests/WaybillTest.php index a6d1358..714eac4 100644 --- a/tests/WaybillTest.php +++ b/tests/WaybillTest.php @@ -74,4 +74,26 @@ public function test_save_on_another_way(): void unlink(realpath(__DIR__.'/../dist').DIRECTORY_SEPARATOR.'test.pdf'); } + + public function test_to_string(): void + { + $waybill = Waybill::of(ParcelService::Cj); + + $this->assertEquals('CJ택배', (string) $waybill); + } + + public function test_download(): void + { + $waybill = Waybill::of(ParcelService::Cj); + + ob_start(); + + $waybill->download('test_download.pdf'); + + $content = ob_get_contents(); + + ob_end_clean(); + + $this->assertNotEmpty($content); + } }