diff --git a/.gitignore b/.gitignore index 57872d0..9940a70 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +/build/ /vendor/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a08914b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,202 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + + +## [Unreleased] + + +## [0.2.0] - 2018-06-01 + +### Added +- Year 2018 in LICENSE +- README: + - Repository title + - Index + - Setup +- Changelog +- Documentation +- Dependencies: + - [aryelgois/databases] + - [aryelgois/medools-router] + - [aryelgois/yasql-php] + - [symfony/yaml] + - Platform requirements: + - zlib and zip extensions +- Composer scripts: + - [aryelgois/yasql-php] +- Config files: + - Builder config for [aryelgois/yasql-php] + - Return file parser options for Banco do Nordeste's CNAB400 schema + - Router config +- Database: + - Tables: + - `assignments` + - `currency_codes` + - `document_kinds` + - `shipping_file_movements` + - Columns: + - `currency_codes.billet` + - `assignments.document_kind` + - `titles.accept` + - `assignments.cnab` + - `titles.doc_number` + - `titles.interest_*` + - `assignments.agency_account_cd` + - `titles.tax_included` + - `titles.shipping_file` + - `titles.movement` + - `shipping_files.notes` + - `titles.emission` + - `titles.protest_*` + - `titles.occurrence_*` + - Index keys for `assignment` and `client` in `titles` + - Populate `wallets` for Banco do Nordeste + - SQL Programs + - Authentication database +- Namespace `aryelgois\BankInterchange\ReturnFile` +- Namespace `aryelgois\BankInterchange\FilePack` +- BankBillet: + - Generic table layouts + - Can generate a `.zip` with multiple bank billets + - `View::updateDictionary()` + - Simple template syntax + - Allows dynamic access to any data in the view + - Supported by: `demonstrative`, `instructions`, `header_info` +- Models: + - `ShippingFile::getTitles()` + - `Title::getCurrencyCode()` + - Mode `nomask` in `Currency::format()` + - `Person` model extending `\aryelgois\Medools\Models\Person` + - `Title::getActualValue()` +- ShippingFile: + - `View::TITLE_LIMIT` + - Bank specific views + - `View::date()` + - Optional 'R' segment in CNAB240 + - Movement masks +- Utils: + - `addExtension()` + - `cleanSpaces()` + - `toPascalCase()` +- `public/` + +### Changed +- Update README +- Update dependencies +- Database: + - Convert to [YASQL][aryelgois/yasql] + - Split `assignors` into `assignors` and `assignments` + - Rename `payers` to `clients` and bound them to `assignors` + - Replace `assignor` column with `assignment` in `titles` + - Split `currencies` into `currencies` and `currency_codes` + - Rename `tax` column to `billet_tax` + - Rename `iof` column to `ioc_iof` + - Use `document_kinds` in `titles` + - Split `discount` columns into multiple discounts + - Change `assignors` PRIMARY KEY to `person` + - Move `address` column from `assignors` to `assignments` + - Rename `billet_tax` column in `titles` to `tax_value` + - Title `fine_type`, `interest_type` and `discount*_type` are `tinyint` have + default value +- Config files: + - ReturnFile: + - Rewrite configs in [YAML] + - Split parser config into individual files + - Improve patterns + - Rename some fields + - Improve Parser + - Update [aryelgois/Medools] config file +- Assignor and Payer names in BankBillet fields +- Update populate wallets +- Move BankBillet classes to its own namespace +- Invert default parameter value for some methods in BankBillet view +- Use bank name in PascalCase to select the BankBillet view +- Allow multiple paths to be searched for logos +- Use model id to select logo file +- Replace `$dictionary` with `$fields` +- Rename `$billet` to `$data` +- Replace `drawTableRow()` and `drawTableColumn()` with `drawRow()` +- Rewrite BankBillet Controller +- Move ShippingFile classes to its own namespace +- Rewrite ShippingFile Controller +- Rewrite ShippingFile View +- Move resource files to assets directory +- Replace `SPECIE_DOC` with title's `kind` +- Rewrite ShippingFile Cnab* views +- BankBillet and ShippingFile Controllers and Views use FilePack +- Convert billet data to [YAML] +- Use `class` keyword in foreign classes + +### Removed +- Accidentally committed lines +- Alias 'BankI' for `aryelgois\BankInterchange` +- `BankBillet\View::beforeDraw()` +- Example of assignor logos +- Column `titles.doc_type` +- Table `shipping_file_titles` +- `Title::setOurNumber()` +- Old ReturnFile model +- Columns `titles.status` and `shipping_files.status` +- ReturnFile Controller +- `Utils::padAlfa()` +- Old example + +### Fixed +- Shipping File counter: using `id` is inconsistent when generating shipping + files for more than one assignor +- Cnab240 View: wrong registry type in 'Q' segment and wrong registry count +- Our Number check digit for Banco do Nordeste has a different length and base +- Currency code for different banks and CNABs +- Remove EOF character `0x1A` in ShippingFile View +- Rename `B. do Nordeste` to `Banco do Nordeste` +- BankBillet views +- Rename `formated` to `formatted` +- Comparison operators +- Database populate collation + + +## [0.1] - 2017-11-27 + +### Added +- Dependencies: + - [aryelgois/Medools] + - [vria/nodiacritic] + - [setasign/fpdf] +- Config file for [aryelgois/Medools] +- Database schema and defaults in SQL +- Some bank logos (and assignor example logos) + +### Changed +- Rename project from `cnab240` to `bank-interchange` +- Bump [aryelgois/utils] version +- Example from a moderate complex twig to a simpler HTML + JavaScript +- Code and logic mostly rewritten + +### Removed +- Obsolete dependency aryelgois/objects +- Dev dependency [twig/twig] + +### Fixed +- README + + +[Unreleased]: https://github.com/aryelgois/bank-interchange/compare/v0.2.0...develop +[0.2.0]: https://github.com/aryelgois/bank-interchange/compare/v0.1...v0.2.0 +[0.1]: https://github.com/aryelgois/bank-interchange/compare/288be2a584bca48feab56f750fe8c51804f0e7ab...v0.1 + +[aryelgois/databases]: https://github.com/aryelgois/databases +[aryelgois/Medools]: https://github.com/aryelgois/Medools +[aryelgois/medools-router]: https://github.com/aryelgois/medools-router +[aryelgois/utils]: https://github.com/aryelgois/utils +[aryelgois/yasql]: https://github.com/aryelgois/yasql +[aryelgois/yasql-php]: https://github.com/aryelgois/yasql-php +[setasign/fpdf]: https://github.com/setasign/fpdf +[symfony/yaml]: https://github.com/symfony/yaml +[twig/twig]: https://github.com/twig/twig +[vria/nodiacritic]: https://github.com/vria/nodiacritic + +[YAML]: http://yaml.org/ diff --git a/LICENSE b/LICENSE index 54c5f3b..e4ef840 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Aryel Mota Góis +Copyright (c) 2017, 2018 Aryel Mota Góis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 981aaa1..0b355cc 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,171 @@ -# Intro +# Bank Interchange + +Index: + +- [Intro] + - [pt_BR] | [en_US] +- [Setup] +- [TODO] +- [Documentation] +- [Changelog] -## (pt_BR) -Esse pacote implementa as especificações do CNAB240 e do CNAB400, definido pelo -FEBRABAN, e geradores de boletos para diversos bancos, em PHP. +## Intro -O CNAB permite a comunicação entre empresas e bancos, organizando as informações -em arquivos de texto com uma estrutura predefinida. +#### pt_BR -O objetivo desse pacote é automatizar a criação de Boletos bancários e Arquivos -Remessa, e a leitura de Arquivos Retorno em um servidor web: +Esse pacote implementa +as especificações do CNAB240 e do CNAB400, +definido pelo FEBRABAN, +e contém geradores de boleto +para diversos bancos, +em PHP. -- O Boleto seria gerado quando o cliente realizasse uma compra, por exemplo. -- O Arquivo Remessa seria gerado logo em seguida, devendo ser enviado ao banco - antes que o cliente efetue o pagamento. -- O Arquivo Retorno enviado pelo banco informa se o pagamento foi efetuado, além - de outros detalhes, e ativaria alguns processos automáticos no servidor. +> O CNAB permite a comunicação +> entre empresas e bancos, +> organizando as informações em arquivos de texto +> com uma estrutura predefinida +O objetivo desse pacote é +automatizar a criação de Boletos bancários +e Arquivos Remessa, +e facilitar a leitura de Arquivos Retorno +em um servidor web: -## (en_US) +1. Quando o cliente realiza uma compra, + por exemplo, + um Título bancário é criado -This package implements CNAB240 and CNAB400 specifications, defined by FEBRABAN -(a Brazilian organization), and bank billet generator for various banks, in PHP. + - Esse Título pode ser representado + como um boleto, + em PDF -The CNAB allows a comunication between enterprises and banks, organizing the -information in text files with a predefined layout. +2. Um Arquivo Remessa, + contendo um ou mais Títulos, + é gerado e enviado ao banco + antes que o cliente efetue o pagamento -This package aims to automatize the generation of Bank billets and Shipping -Files, and the reading of Return Files in a webserver: +3. O banco envia um Arquivo Retorno + informando se o Título foi + aceito, + pago, + tem algum erro, + ou alguma outra ocorrência -- The billet would be generated when your client buys something, for example. -- The Shipping File would be generated soon after, and should be sent to the - bank before the client makes the payment. - billets of that day. -- The Return File sent by the bank tells if the payment was accomplished, - besides other details, and would trigger some hooks in the server. +4. Após o administrador conferir o resultado, + o banco de dados é atualizado + com novos dados -# Example +#### en_US -There is a well designed example you can explore! It shows a simple way to -implement the package in a website. +This package implements +CNAB240 and CNAB400 specifications, +defined by FEBRABAN (a Brazilian organization), +and contains bank billet generators +for various banks, +in PHP. -You can insert data in the Database, generate bank billets and shipping files. -These shipping files can be viewed in both CNAB240 and CNAB400. Also, there is -a simple Return File analyzer. +> The CNAB allows a comunication +> between enterprises and banks, +> organizing the information in text files +> with a predefined layout +This package aims to +automate the generation of bank billets +and Shipping Files, +and to help reading Return Files +in a web server: -# TODO +1. When your client buys something, + for exemple, + a banking Title is created -The script kinda works.. It's under development. + - This Title can be rendered + as a bank billet, + in PDF -- [ ] Code review -- [ ] Real world test CNAB240 and CNAB400. -- [x] Write the Return File interpreter for CNAB240 and CNAB400. - - [x] Make it interact with the Database - - [ ] It should receive the data somehow.. fetch from the bank's site or - provide a user input? -- [ ] Create hooks for Return Files. - - [ ] A nice interface to integrate with one's website. +2. A Shipping File, + containing one or more Titles, + is generated and sent to the bank + before the client makes the payment + +3. The bank sends a Return File + informing if the Title was + accepted, + paid, + has an error, + or some other occurrence + +4. After the administrator checks the result, + the database is updated + with new data + + +## Setup + +1. Clone with Git + +2. Create a web server with PHP 7 or higher + + - Using Apache is recommended + because the `.htaccess` files are already created + + - Enable the `AllowOverride` directive + + - Set the Document Root to `public/` + + - If you are using HTTPS, + uncomment the line with `SSLRequireSSL` directive + in `public/.htaccess`. + It is highly recommended that you use SSL + +3. Build the databases + and run the generated SQL: + _see [YASQL-PHP][aryelgois/yasql-php]_ + + ```bash +composer yasql-build && cat build/*.sql | mysql -u root -p + ``` + +4. Change the database credentials in + `config/medools.php` + _see [Medools][aryelgois/Medools]_ + +5. Configure the Authentication secret in + `config/router.yml` + _see [Medools Router][aryelgois/medools-router]_ + + - You also need to register users + in the `authentications` table + and add their `authorizations` + +6. Now you can develop inside `public/` + + - Make your app ajax request from server's `/api/` + + +## TODO + +- [ ] Real world tests +- [ ] A nice web interface + + +## [Documentation] + + +## [Changelog] + + +[Intro]: #intro +[pt_BR]: #pt_br +[en_US]: #en_us +[Setup]: #setup +[TODO]: #todo + +[Documentation]: doc/README.md +[Changelog]: CHANGELOG.md + +[aryelgois/medools]: https://github.com/aryelgois/Medools +[aryelgois/medools-router]: https://github.com/aryelgois/medools-router +[aryelgois/yasql-php]: https://github.com/aryelgois/yasql-php diff --git a/assets/logos/assignors/.gitignore b/assets/logos/assignors/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/assets/logos/assignors/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/res/logos/banks/caixa.jpg b/assets/logos/banks/1.jpg similarity index 100% rename from res/logos/banks/caixa.jpg rename to assets/logos/banks/1.jpg diff --git a/res/logos/banks/banese.jpg b/assets/logos/banks/2.jpg similarity index 100% rename from res/logos/banks/banese.jpg rename to assets/logos/banks/2.jpg diff --git a/res/logos/banks/banco_do_nordeste.jpg b/assets/logos/banks/3.jpg similarity index 100% rename from res/logos/banks/banco_do_nordeste.jpg rename to assets/logos/banks/3.jpg diff --git a/composer.json b/composer.json index c227d2b..2cd7b40 100644 --- a/composer.json +++ b/composer.json @@ -11,14 +11,24 @@ ], "require": { "php": "^7.0", - "aryelgois/utils": "^0.2", - "aryelgois/medools": "^2.0", - "vria/nodiacritic": "0.*", - "setasign/fpdf": "^1.8" + "ext-zip": "*", + "ext-zlib": "*", + "aryelgois/databases": "^0.7", + "aryelgois/medools": "^5.1", + "aryelgois/medools-router": "^0.3", + "aryelgois/utils": "^0.5", + "aryelgois/yasql-php": "^0.6", + "setasign/fpdf": "^1.8", + "symfony/yaml": "^3.4", + "vria/nodiacritic": "0.*" }, "autoload": { "psr-4": { "aryelgois\\BankInterchange\\": "src/" } + }, + "scripts": { + "yasql-build": "aryelgois\\YaSql\\Composer::build", + "yasql-generate": "aryelgois\\YaSql\\Composer::generate" } } diff --git a/composer.lock b/composer.lock index 9c72b5b..0629195 100644 --- a/composer.lock +++ b/composer.lock @@ -1,30 +1,79 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "29a6cc3ccbad2db70d2089c6b0536735", + "content-hash": "1d71057d2f69c5c35e41a371c896c69b", "packages": [ + { + "name": "aryelgois/databases", + "version": "v0.7.1", + "source": { + "type": "git", + "url": "https://github.com/aryelgois/databases.git", + "reference": "b624f3f71126b6de7c13fa9581c7a368b8ecfdf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aryelgois/databases/zipball/b624f3f71126b6de7c13fa9581c7a368b8ecfdf4", + "reference": "b624f3f71126b6de7c13fa9581c7a368b8ecfdf4", + "shasum": "" + }, + "require": { + "aryelgois/medools": "^5.1", + "aryelgois/yasql-php": "^0.6.0", + "php": "^7.0", + "symfony/yaml": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "aryelgois\\Databases\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aryel Mota Góis", + "role": "Developer" + } + ], + "description": "Some useful databases for your projects", + "keywords": [ + "database", + "medools", + "schema", + "yasql" + ], + "time": "2018-06-01T17:03:44+00:00" + }, { "name": "aryelgois/medools", - "version": "v2.0", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/aryelgois/Medools.git", - "reference": "f011521c1c8a092cb6042a0d72dc7b61db158968" + "reference": "cdae1cca42e2ed518e5a62c4c34832d9afd1eb7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aryelgois/Medools/zipball/f011521c1c8a092cb6042a0d72dc7b61db158968", - "reference": "f011521c1c8a092cb6042a0d72dc7b61db158968", + "url": "https://api.github.com/repos/aryelgois/Medools/zipball/cdae1cca42e2ed518e5a62c4c34832d9afd1eb7d", + "reference": "cdae1cca42e2ed518e5a62c4c34832d9afd1eb7d", "shasum": "" }, "require": { - "aryelgois/utils": "^0.2", - "catfan/medoo": "^1.4", + "aryelgois/utils": "^0.5", + "catfan/medoo": "^1.5", "php": "^7.0" }, + "suggest": { + "aryelgois/databases": "For some databases and models already created", + "aryelgois/yasql-php": "For creating database schemas" + }, "type": "library", "autoload": { "psr-4": { @@ -44,22 +93,69 @@ "description": "Wrapper on catfan/Medoo", "keywords": [ "database", - "medoo" + "medoo", + "medools" + ], + "time": "2018-05-28T18:17:43+00:00" + }, + { + "name": "aryelgois/medools-router", + "version": "v0.3.2", + "source": { + "type": "git", + "url": "https://github.com/aryelgois/medools-router.git", + "reference": "ee5a9d926184259367b5568c72e885b7868ebdd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aryelgois/medools-router/zipball/ee5a9d926184259367b5568c72e885b7868ebdd0", + "reference": "ee5a9d926184259367b5568c72e885b7868ebdd0", + "shasum": "" + }, + "require": { + "aryelgois/medools": "^5.1", + "aryelgois/utils": "^0.5", + "aryelgois/yasql-php": "^0.6.0", + "ext-zlib": "*", + "firebase/php-jwt": "^5.0", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "aryelgois\\MedoolsRouter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], - "time": "2017-11-18T19:36:58+00:00" + "authors": [ + { + "name": "Aryel Mota Góis", + "role": "Developer" + } + ], + "description": "A Router framework to bootstrap RESTful APIs based on aryelgois/Medools", + "keywords": [ + "api", + "medools", + "restful" + ], + "time": "2018-06-01T00:29:30+00:00" }, { "name": "aryelgois/utils", - "version": "v0.2.1", + "version": "v0.5.1", "source": { "type": "git", "url": "https://github.com/aryelgois/utils.git", - "reference": "dcd2d5bd5866e4feaa1ad1e1a83c9b4f4217c3b0" + "reference": "683a4685de166592aadd0fe7ebec828b99f0cbdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aryelgois/utils/zipball/dcd2d5bd5866e4feaa1ad1e1a83c9b4f4217c3b0", - "reference": "dcd2d5bd5866e4feaa1ad1e1a83c9b4f4217c3b0", + "url": "https://api.github.com/repos/aryelgois/utils/zipball/683a4685de166592aadd0fe7ebec828b99f0cbdc", + "reference": "683a4685de166592aadd0fe7ebec828b99f0cbdc", "shasum": "" }, "require": { @@ -85,20 +181,65 @@ "keywords": [ "utils" ], - "time": "2017-11-19T12:27:56+00:00" + "time": "2018-03-10T13:03:12+00:00" + }, + { + "name": "aryelgois/yasql-php", + "version": "v0.6.0", + "source": { + "type": "git", + "url": "https://github.com/aryelgois/yasql-php.git", + "reference": "f428b180d35bfc1ddf1f9b3323f9b085fbc09ac7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aryelgois/yasql-php/zipball/f428b180d35bfc1ddf1f9b3323f9b085fbc09ac7", + "reference": "f428b180d35bfc1ddf1f9b3323f9b085fbc09ac7", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/yaml": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "aryelgois\\YaSql\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aryel Mota Góis", + "role": "Developer" + } + ], + "description": "A PHP implementation for YAML Ain't SQL", + "keywords": [ + "database", + "php", + "schema", + "sql", + "yaml", + "yasql" + ], + "time": "2018-05-28T19:27:29+00:00" }, { "name": "catfan/medoo", - "version": "v1.4.5", + "version": "v1.5.6", "source": { "type": "git", "url": "https://github.com/catfan/Medoo.git", - "reference": "8ee8ae1c4df0fee3822a01333eab88893e548fcc" + "reference": "f77a93f72864e892c99d1033b8733e5da8fb0b3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/catfan/Medoo/zipball/8ee8ae1c4df0fee3822a01333eab88893e548fcc", - "reference": "8ee8ae1c4df0fee3822a01333eab88893e548fcc", + "url": "https://api.github.com/repos/catfan/Medoo/zipball/f77a93f72864e892c99d1033b8733e5da8fb0b3b", + "reference": "f77a93f72864e892c99d1033b8733e5da8fb0b3b", "shasum": "" }, "require": { @@ -112,7 +253,7 @@ "ext-pdo_oci8": "For Oracle version 8 database", "ext-pdo_pqsql": "For PostgreSQL database", "ext-pdo_sqlite": "For SQLite database", - "ext-pdo_sqlsrv": "For MSSQL database on Windows platform" + "ext-pdo_sqlsrv": "For MSSQL database" }, "type": "framework", "autoload": { @@ -144,7 +285,53 @@ "sql", "sqlite" ], - "time": "2017-06-24T14:43:07+00:00" + "time": "2018-03-26T17:54:24+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v5.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": " 4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "time": "2017-06-27T22:17:23+00:00" }, { "name": "setasign/fpdf", @@ -185,6 +372,120 @@ ], "time": "2016-01-01T17:47:15+00:00" }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-04-30T19:57:29+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c5010cc1692ce1fa328b1fb666961eb3d4a85bb0", + "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-05-03T23:18:14+00:00" + }, { "name": "vria/nodiacritic", "version": "0.1.2", @@ -241,7 +542,9 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.0" + "php": "^7.0", + "ext-zip": "*", + "ext-zlib": "*" }, "platform-dev": [] } diff --git a/config/billet.yml b/config/billet.yml new file mode 100644 index 0000000..7ef5b84 --- /dev/null +++ b/config/billet.yml @@ -0,0 +1,26 @@ +header_title: Instruções de Impressão + +header_body: |- + - Imprima em impressora jato de tinta (ink jet) ou laser em qualidade normal ou alta (Não use modo econômico). + - Utilize folha A4 (210 x 297 mm) ou Carta (216 x 279 mm) e margens mínimas à esquerda e à direita do formulário. + - Corte na linha indicada. Não rasure, risque, fure ou dobre a região onde se encontra o código de barras. + - Caso não apareça o código de barras no final, clique em F5 para atualizar esta tela. + - Caso tenha problemas ao imprimir, copie a sequência numérica abaixo e pague no caixa eletrônico ou no internet banking: + +header_info: |1 + Linha Digitável: {{ $data->digitable }} + Valor: {{ $data->value }} + +payment_place: Pagável em qualquer Banco até o vencimento + +demonstrative: |- + Pagamento de boleto - Teste de boleto + Taxa bancária - {{ tax_value }} + {{ description }} + BankInterchange - https://www.github.com/aryelgois/bank-interchange + +instructions: |- + - Sr. Caixa, cobrar multa de 2% após o vencimento + - Receber até 10 dias após o vencimento + - Em caso de dúvidas entre em contato conosco: suporte@exemplo.com.br + Emitido pelo sistema BankInterchange - https://www.github.com/aryelgois/bank-interchange diff --git a/config/databases.yml b/config/databases.yml new file mode 100644 index 0000000..e0ee138 --- /dev/null +++ b/config/databases.yml @@ -0,0 +1,10 @@ +databases: + - path: ../data/bank_interchange.yml + post: + - ../data/bank_interchange_programs.sql + - ../data/bank_interchange_populate.sql + - path: ../vendor/aryelgois/medools-router/data/authentication.yml + name: bank_interchange + +vendors: + aryelgois/databases: ~ diff --git a/config/medools.php b/config/medools.php index 9eba608..f337893 100644 --- a/config/medools.php +++ b/config/medools.php @@ -1,23 +1,26 @@ [ + 'default' => [ + // required + 'server' => 'localhost', + 'username' => 'root', + 'password' => 'password', + 'database_type' => 'mysql', + + // [optional] + 'charset' => 'utf8', + ], + ], 'databases' => [ - 'default' => 'bank_interchange', - 'address' => 'address', + 'default' => 'bank_interchange', + 'authentication' => 'bank_interchange', + 'address' => 'address', ], - 'options' => [ - // required - 'database_type' => 'mysql', - 'server' => 'localhost', - 'username' => 'root', - 'password' => 'password', - - // [optional] - 'charset' => 'utf8', - ] ]; diff --git a/config/return_file/cnab240/047.yml b/config/return_file/cnab240/047.yml new file mode 100644 index 0000000..ff929b4 --- /dev/null +++ b/config/return_file/cnab240/047.yml @@ -0,0 +1,366 @@ +# Banese + +structure: + - FILE_HEADER + - + - LOT_HEADER + - + - TITLE_T TITLE_U + - LOT_TRAILER + - FILE_TRAILER + +registries: + FILE_HEADER: + pattern: /^(047)(\d{4})0 {9}(\d)(\d{14})(\d{20})(\d{5})(.)(\d{12})(.)(.)(.{30})(.{30}) {10}2(\d{8})(\d{6})(\d{6})\d{3}\d{5}(.{20})(.{20}) {29}$/ + map: + - bank_code + - lot + - assignor_document_type + - assignor_document + - assignment_covenant + - assignment_agency + - assignment_agency_cd + - assignment_account + - assignment_account_cd + - assignment_agency_account_cd + - assignor_name + - bank_name + - record_date + - record_time + - file_sequence + - bank_use + - assignor_use + + LOT_HEADER: + pattern: /^(047)(\d{4})1T(\d{2}) {2}\d{3} (\d)(\d{15})(\d{20})(\d{5})(.)(\d{12})(.)(.)(.{30})(.{40})(.{40})(\d{8})(\d{8})(\d{8}) {33}$/ + map: + - bank_code + - lot + - service_code + - assignor_document_type + - assignor_document + - assignment_covenant + - assignment_agency + - assignment_agency_cd + - assignment_account + - assignment_account_cd + - assignment_agency_account_cd + - assignor_name + - message1 + - message2 + - shipping_return_number + - shipping_return_record_date + - credit_date + + TITLE_T: + pattern: /^(047)(\d{4})3(\d{5})T (\d{2})(\d{5})(.)(\d{12})(.)(.)(\d{19})(\d)(\d)(.{15})(\d{8})(\d{15})(\d{3})(\d{5})(\d)(.{25})(\d{2})(\d)(\d{15})(.{40})(\d{10})(\d{15})(.{10}) {17}$/ + map: + - bank_code + - lot + - lot_registry + - movement + - assignment_agency + - assignment_agency_cd + - assignment_account + - assignment_account_cd + - assignment_agency_account_cd + - our_number + - our_number_cd + - wallet + - doc_number + - due + - value + - receiver_bank + - receiver_agency + - receiver_agency_cd + - assignor_use + - currency + - payer_document_type + - payer_document + - payer_name + - contract + - tax + - occurrence + + TITLE_U: + pattern: /^(047)(\d{4})3(\d{5})U (\d{2})(\d{15})(\d{15})(\d{15})(\d{15})(\d{15})(\d{15})(\d{15})(\d{15})(\d{8})(\d{8})(.{4})(.{8})(\d{15})(.{30})(\d{3})(\d{19})(\d) {7}$/ + map: + - bank_code + - lot + - lot_registry + - movement + - fine_value + - discount_value + - rebate + - ioc_iof + - value_received + - value_net + - expenses + - credits + - occurrence_date + - credit_date + - payer_occurrence_code + - payer_occurrence_date + - payer_occurrence_value + - payer_occurrence_detail + - corresponding_bank + - corresponding_bank_our_number + - corresponding_bank_our_number_cd + + LOT_TRAILER: + pattern: /^(047)(\d{4})5 {9}(\d{6})(\d{6})(\d{17})(\d{6})(\d{17})(\d{6})(\d{17})(\d{6})(\d{17})(.{8}) {117}$/ + map: + - bank_code + - lot + - lot_registry_count + - cs_count + - cs_total + - cv_count + - cv_total + - cc_count + - cc_total + - cd_count + - cd_total + - warning + + FILE_TRAILER: + pattern: /^(047)(\d{4})9 {9}(\d{6})(\d{6})(\d{6}) {205}$/ + map: + - bank_code + - lot + - lot_count + - registry_count + - reconciliation_accounts_count + +movement: + '02': Entrada Confirmada + '03': Entrada Rejeitada + '04': Transferência de Carteira/Entrada + '05': Transferência de Carteira/Baixa + '06': Liquidação + '07': Confirmação do Recebimento da Instrução de Desconto + '08': Confirmação do Recebimento do Cancelamento do Desconto + '09': Baixa + '11': Títulos em Carteira (Em Ser) + '12': Confirmação Recebimento Instrução de Abatimento + '13': Confirmação Recebimento Instrução de Cancelamento Abatimento + '14': Confirmação Recebimento Instrução Alteração de Vencimento + '15': Franco de Pagamento + '17': Liquidação Após Baixa ou Liquidação Título Não Registrado + '19': Confirmação Recebimento Instrução de Protesto + '20': Confirmação Recebimento Instrução de Sustação/Cancelamento de Protesto + '23': Remessa a Cartório (Aponte em Cartório) + '24': Retirada de Cartório e Manutenção em Carteira + '25': Protestado e Baixado (Baixa por Ter Sido Protestado) + '26': Instrução Rejeitada + '27': Confirmação do Pedido de Alteração de Outros Dados + '28': Débito de Tarifas/Custas + '29': Ocorrências do Pagador + '30': Alteração de Dados Rejeitada + '33': Confirmação da Alteração dos Dados do Rateio de Crédito + '34': Confirmação do Cancelamento dos Dados do Rateio de Crédito + '35': Confirmação do Desagendamento do Débito Automático + '36': Confirmação de envio de e-mail/SMS + '37': Envio de e-mail/SMS rejeitado + '38': Confirmação de alteração do Prazo Limite de Recebimento (a data deve ser informada no campo 28.3.p) + '39': Confirmação de Dispensa de Prazo Limite de Recebimento + '40': Confirmação da alteração do número do título dado pelo Beneficiário + '41': Confirmação da alteração do número controle do Participante + '42': Confirmação da alteração dos dados do Pagador + '43': Confirmação da alteração dos dados do Sacador/Avalista + '44': Título pago com cheque devolvido + '45': Título pago com cheque compensado + '46': Instrução para cancelar protesto confirmada + '47': Instrução para protesto para fins falimentares confirmada + '48': Confirmação de instrução de transferência de carteira/modalidade de cobrança + '49': Alteração de contrato de cobrança + '50': Título pago com cheque pendente de liquidação + '51': Título DDA reconhecido pelo Pagador + '52': Título DDA não reconhecido pelo Pagador + '53': Título DDA recusado pela CIP + '54': Confirmação da Instrução de Baixa de Título Negativado sem Protesto + '55': Confirmação de Pedido de Dispensa de Multa + '56': Confirmação do Pedido de Cobrança de Multa + '57': Confirmação do Pedido de Alteração de Cobrança de Juros + '58': Confirmação do Pedido de Alteração do Valor/Data de Desconto + '59': Confirmação do Pedido de Alteração do Beneficiário do Título + '60': Confirmação do Pedido de Dispensa de Juros de Mora + '61': Confirmação de Alteração do Valor Nominal do Título + '63': Título Sustado Judicialmente + '64': Confirmação de alteração do valor mínimo/percentual + '65': Confirmação de alteração do valor máximo/percentual + +occurrence: + A: + '01': Código do Banco Inválido + '02': Código do Registro Detalhe Inválido + '03': Código do Segmento Inválido + '04': Código de Movimento Não Permitido para Carteira + '05': Código de Movimento Inválido + '06': Tipo/Número de Inscrição do Beneficiário Inválidos + '07': Agência/Conta/DV Inválido + '08': Nosso Número Inválido + '09': Nosso Número Duplicado + '10': Carteira Inválida + '11': Forma de Cadastramento do Título Inválido + '12': Tipo de Documento Inválido + '13': Identificação da Emissão do Boleto de Pagamento Inválida + '14': Identificação da Distribuição do Boleto de Pagamento Inválida + '15': Características da Cobrança Incompatíveis + '16': Data de Vencimento Inválida + '17': Data de Vencimento Anterior a Data de Emissão + '18': Vencimento Fora do Prazo de Operação + '19': Título a Cargo de Bancos Correspondentes com Vencimento Inferior a XX Dias + '20': Valor do Título Inválido + '21': Espécie do Título Inválida + '22': Espécie do Título Não Permitida para a Carteira + '23': Aceite Inválido + '24': Data da Emissão Inválida + '25': Data da Emissão Posterior a Data de Entrada + '26': Código de Juros de Mora Inválido + '27': Valor/Taxa de Juros de Mora Inválido + '28': Código do Desconto Inválido + '29': Valor do Desconto Maior ou Igual ao Valor do Título + '30': Desconto a Conceder Não Confere + '31': Concessão de Desconto - Já Existe Desconto Anterior + '32': Valor do IOF Inválido + '33': Valor do Abatimento Inválido + '34': Valor do Abatimento Maior ou Igual ao Valor do Título + '35': Valor a Conceder Não Confere + '36': Concessão de Abatimento - Já Existe Abatimento Anterior + '37': Código para Protesto Inválido + '38': Prazo para Protesto Inválido + '39': Pedido de Protesto Não Permitido para o Título + '40': Título com Ordem de Protesto Emitida + '41': Pedido de Cancelamento/Sustação para Títulos sem Instrução de Protesto + '42': Código para Baixa/Devolução Inválido + '43': Prazo para Baixa/Devolução Inválido + '44': Código da Moeda Inválido + '45': Nome do Pagador Não Informado + '46': Tipo/Número de Inscrição do Pagador Inválidos + '47': Endereço do Pagador Não Informado + '48': CEP Inválido + '49': CEP Sem Praça de Cobrança (Não Localizado) + '50': CEP Referente a um Banco Correspondente + '51': CEP incompatível com a Unidade da Federação + '52': Unidade da Federação Inválida + '53': Tipo/Número de Inscrição do Sacador/Avalista Inválidos + '54': Sacador/Avalista Não Informado + '55': Nosso número no Banco Correspondente Não Informado + '56': Código do Banco Correspondente Não Informado + '57': Código da Multa Inválido + '58': Data da Multa Inválida + '59': Valor/Percentual da Multa Inválido + '60': Movimento para Título Não Cadastrado + '61': Alteração da Agência Cobradora/DV Inválida + '62': Tipo de Impressão Inválido + '63': Entrada para Título já cadastrado + '64': Número da Linha Inválido + '65': Código do Banco para Débito Inválido + '66': Agência/Conta/DV para Débito Inválido + '67': Dados para Débito incompatível com a Identificação da Emissão do Boleto de Pagamento + '68': Débito Automático Agendado + '69': Débito Não Agendado - Erro nos Dados da Remessa + '70': Débito Não Agendado - Pagador Não Consta do Cadastro de Autorizante + '71': Débito Não Agendado - Beneficiário Não Autorizado pelo Pagador + '72': Débito Não Agendado - Beneficiário Não Participa da Modalidade Débito Automático + '73': Débito Não Agendado - Código de Moeda Diferente de Real (R$) + '74': Débito Não Agendado - Data Vencimento Inválida + '75': Débito Não Agendado, conforme seu Pedido, Título Não Registrado + '76': Débito Não Agendado, Tipo/Num. Inscrição do Debitado, Inválido + '77': Transferência para Desconto Não Permitida para a Carteira do Título + '78': Data Inferior ou Igual ao Vencimento para Débito Automático + '79': Data Juros de Mora Inválido + '80': Data do Desconto Inválida + '81': Tentativas de Débito Esgotadas - Baixado + '82': Tentativas de Débito Esgotadas - Pendente + '83': Limite Excedido + '84': Número Autorização Inexistente + '85': Título com Pagamento Vinculado + '86': Seu Número Inválido + '87': e-mail/SMS enviado + '88': e-mail Lido + '89': e-mail/SMS devolvido - endereço de e-mail ou número do celular incorreto + '90': e-mail devolvido - caixa postal cheia + '91': e-mail/número do celular do Pagador não informado + '92': Pagador optante por Boleto de Pagamento Eletrônico - e-mail não enviado + '93': Código para emissão de Boleto de Pagamento não permite envio de e-mail + '94': Código da Carteira inválido para envio e-mail + '95': Contrato não permite o envio de e-mail + '96': Número de contrato inválido + '97': Rejeição da alteração do prazo limite de recebimento (a data deve ser informada no campo 28.3.p) + '98': Rejeição de dispensa de prazo limite de recebimento + '99': Rejeição da alteração do número do título dado pelo Beneficiário + 'A1': Rejeição da alteração do número controle do participante + 'A2': Rejeição da alteração dos dados do Pagador + 'A3': Rejeição da alteração dos dados do Sacador/avalista + 'A4': Pagador DDA + 'A5': Registro Rejeitado – Título já Liquidado + 'A6': Código do Convenente Inválido ou Encerrado + 'A7': Título já se encontra na situação Pretendida + 'A8': Valor do Abatimento inválido para cancelamento + 'A9': Não autoriza pagamento parcial + 'B1': Autoriza recebimento parcial + 'B2': Valor Nominal do Título Conflitante + 'B3': Tipo de Pagamento Inválido + 'B4': Valor Máximo/Percentual Inválido + 'B5': Valor Mínimo/Percentual Inválido + 'ZY': Já existe um movimento (Inclusão/Alteração/Baixa) em processamento para esse boleto na Base Centralizada + 'ZZ': Existe mais de um tipo de movimento (Inclusão/Alteração/Baixa) para o mesmo boleto em um mesmo arquivo de remessa + + B: + '01': Tarifa de Extrato de Posição + '02': Tarifa de Manutenção de Título Vencido + '03': Tarifa de Sustação + '04': Tarifa de Protesto + '05': Tarifa de Outras Instruções + '06': Tarifa de Outras Ocorrências + '07': Tarifa de Envio de Duplicata ao Pagador + '08': Custas de Protesto + '09': Custas de Sustação de Protesto + '10': Custas de Cartório Distribuidor + '11': Custas de Edital + '12': Tarifa Sobre Devolução de Título Vencido + '13': Tarifa Sobre Registro Cobrada na Baixa/Liquidação + '14': Tarifa Sobre Reapresentação Automática + '15': Tarifa Sobre Rateio de Crédito + '16': Tarifa Sobre Informações Via Fax + '17': Tarifa Sobre Prorrogação de Vencimento + '18': Tarifa Sobre Alteração de Abatimento/Desconto + '19': Tarifa Sobre Arquivo mensal (Em Ser) + '20': Tarifa Sobre Emissão de Boleto de Pagamento Pré-Emitido pelo Banco + + C: + '01': Por Saldo + '02': Por Conta + '03': Liquidação no Guichê de Caixa em Dinheiro + '04': Compensação Eletrônica + '05': Compensação Convencional + '06': Por Meio Eletrônico + '07': Após Feriado Local + '08': Em Cartório + '09': Comandada Banco + '10': Comandada Cliente Arquivo + '11': Comandada Cliente On-line + '12': Decurso Prazo - Cliente + '13': Decurso Prazo - Banco + '14': Protestado + '15': Título Excluído Valor dos Juros / Multa / Encargo + '30': Liquidação no Guichê de Caixa em Cheque + '31': Liquidação em banco correspondente + '32': Liquidação Terminal de Autoatendimento + '33': Liquidação na Internet (Home banking) + '34': Liquidado Office Banking + '35': Liquidado Correspondente em Dinheiro + '36': Liquidado Correspondente em Cheque + '37': Liquidado por meio de Central de Atendimento (Telefone) + +movement_to_occurrence: + '02': A + '03': A + '06': C + '09': C + '17': C + '26': A + '28': B + '30': A diff --git a/config/return_file/cnab400/004.yml b/config/return_file/cnab400/004.yml new file mode 100644 index 0000000..4f65f84 --- /dev/null +++ b/config/return_file/cnab400/004.yml @@ -0,0 +1,206 @@ +# Banco do Nordeste + +structure: + - HEADER + - + - TRANSACTION + - TRAILER + +registries: + HEADER: + pattern: /^02RETORNO(\d{2}).{15}(\d{4})(\d{2})(\d{7})(\d) {6}(.{30})(004)(.{15})(\d{6}).{8}(\d{5}) {6}(\d{6}) {269}(\d{6})$/ + map: + - service_code + - assignment_agency + - assignment_agency_cd + - assignment_account + - assignment_account_cd + - assignor_name + - bank_code + - bank_name + - record_date + - file_sequence + - credit_date + - registry + + TRANSACTION: + pattern: /^1(\d{2})(\d{14})(\d{4})(\d{2})(\d{7})(\d) {6}(.{25})(\d{7})(\d)(\d{10}) {27}(\d)(\d{2})(\d{6})(.{10})(\d{7})(\d) {12}(\d{6})(\d{13})(\d{3})(\d{4}) (\d{2})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13})(.{115})(\d{6})$/ + map: + - assignor_document_type + - assignor_document + - assignment_agency + - assignment_agency_cd + - assignment_account + - assignment_account_cd + - doc_number + - our_number + - our_number_cd + - contract + - wallet + - occurrence + - occurrence_date + - your_number + - confirm_our_number + - confirm_our_number_cd + - due + - value + - receiver_bank + - receiver_agency + - kind + - tax + - expenses + - interest + - ioc_iof + - rebate + - discount_value + - value_received + - interest_value + - error_table + - registry + + TRAILER: + pattern: /^92(\d{2})(004) {10}(\d{8})(\d{14})(\d{8}) {347}(\d{6})$/ + map: + - service_code + - bank_code + - cs_count + - cs_total + - cs_warning + - registry + +occurrence: + '02': Entrada Confirmada + '04': Alteração + '06': Liquidação Normal + '07': Pagamento por Conta + '08': Pagamento por Cartório + '09': Baixa Simples + '10': Devolvido / Protestado + '11': Em ser + '12': Abatimento Concedido + '13': Abatimento Cancelado + '14': Vencimento Alterado + '15': Baixa Automática + '18': Alteração Depositária + '19': Confirmação de Protesto + '20': Confirmação de Sustar Protesto + '21': Alteração Informações de Controle Empresa + '22': Alteração "Seu Número" + '51': Entrada Rejeitada + +error_table: + - Falta valor do IOC. + - Não permite desconto/ abatimento. + - Código do serviço inválido. + - Novo vencimento igual/ menor que o da entrada. + - Novo vencimento igual ao do Título. + - Espécie Documento Inválida. + - Espécie Documento Inexistente. + - Tipo Operação Inválida. + - Tipo Operação Inexistente. + - Contrato Proibido para esta Carteira. + - Falta Número do Contrato. + - Proibido Informar Tipo de Conta. + - Tipo de Conta do Contrato Inexistente. + - Dígito de Contrato não confere. + - Contrato Inexistente. + - Data de Emissão Inválida. + - Falta Valor do Título. + - Vencimento Inválido. + - Data Vencimento Anterior a Emissão. + - Falta Vencimento Desconto. + - Data Desconto Inválida. + - Data Desconto Posterior ao Vencimento. + - Falta Valor Desconto. + - Falta Mora-1-Dia. + - Banco/Agência Cobrador Inexistente. + - BCO/AGE Cobrador não Cadastrado. + - Código Pessoa Inválido. + - Falta CEP, Banco e Agência Cobrador. + - Falta Nome Sacado. + - Falta Endereço. + - Falta Cidade. + - Falta Estado. + - Estado Inválido. + - Falta CPF/ CGC do Sacado. + - Falta numeração - Bloquete emitido. + - Título Pré-Numerado já Existente. + - Dígito do Título Não Confere. + - Proibido Protestar. + - Proibido título pré-numerado p/ Correspondente. + - Dígito Cliente/ Contrato com Erro. + - Dígito Nosso Número com Erro. + - Título Inexistente. + - Título Liquidado. + - Título Não Pode Ser Baixado. + - Valor Nominal Incorreto. + - Proibido Taxa – Multa p/ Correspondente. + - Falta Tipo de Conta do Contrato. + - Tipo de Conta Inexistente. + - Dígito Contrato Não Confere. + - Dígito do Título Não Confere. + - Título Inexistente ou Liquidado. + - Valor Abatimento Inválido. + - Data Vencimento Inválida. + - Estado Inválido. + - Falta Tipo de Pessoa P/ Alteração de CGC/ CPF. + - CPF/ CGC com Erro. + - Data Emissão Inválida. + - Data Vencimento Desconto Inválida. + - Aceite Inválido para Espécie Documento. + - Não Aceite Inválido para Espécie Documento. + - Banco/ Agência Cobrador Inválido. + - Limite Operacional Não Cadastrado. + - Título já em situação de protesto. + - Proibido alterar vencimento título descontado. + - Proibido informar nosso número p/ cod. carteira. + - Falta vencimento desconto-2. + - Data desconto-2 inválida. + - Data desconto-2 posterior ao vencimento. + - Falta valor desconto-2. + - Data vencimento desconto-2 inválida. + - IOC maior que valor do título. + - CEP não pertence ao Estado. + - Seu número já existente. + - Moeda Inválida para o tipo de Operação. + - Moeda inexistente. + - Nosso número/ dígito com erro. + - Dias vencidos superior ao prazo de devolução. + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO + - VAGO diff --git a/config/return_file/cnab400/047.yml b/config/return_file/cnab400/047.yml new file mode 100644 index 0000000..0da862e --- /dev/null +++ b/config/return_file/cnab400/047.yml @@ -0,0 +1,93 @@ +# Banese + +structure: + - HEADER + - + - TRANSACTION + - TRAILER + +registries: + HEADER: + pattern: /^02RETORNO(\d{2}).{15}\d{8}(\d{3})(\d{8})(\d)(.{30})(047)(.{15})(\d{6})(\d{2})(\d{14}) {273}(\d{5})(\d{6})$/ + map: + - service_code + - assignment_agency + - assignment_account + - assignment_account_cd + - assignor_name + - bank_code + - bank_name + - record_date + - assignor_document_type + - assignor_document + - file_sequence + - registry + + TRANSACTION: + pattern: /^1(\d{2})(\d{14})\d{8}(\d{3})(\d{8})(\d)(.{25})(\d{19})(\d) {25}(\d)(\d{2})(\d{6})(.{10}) {20}(\d{6})(\d{13})(.{3})(\d{5})(\d{2})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13})(\d{13}) {9}(.)(\d{12})(\d{6})(\d{13})(\d{2})(\d{2}) {54}(\d{2})(\d)(\d{6})$/ + map: + - assignor_document_type + - assignor_document + - assignment_agency + - assignment_account + - assignment_account_cd + - doc_number + - our_number + - our_number_cd + - wallet + - occurrence + - occurrence_date + - your_number + - due + - value + - receiver_bank + - receiver_agency + - kind + - tax + - expenses + - interest + - ioc_iof + - rebate + - discount_value + - value_received + - interest_value + - credits + - confirm_interest + - confirm_interest_charge + - confirm_discount + - confirm_discount_value + - confirm_instruction1 + - confirm_instruction2 + - confirm_protest + - currency + - registry + + TRAILER: + pattern: /^92(\d{2})(047) {10}(\d{8})(\d{14})(\d{8}) {10}(\d{8})(\d{14})(\d{8}) {10}(\d{8})(\d{14})(\d{8}) {10}(\d{8})(\d{14})(\d{8}) {10}(\d{14})(\d{3})(\d{14})(\d{14})(\d{14})(\d{14}) {144}(\d{6})$/ + map: + - service_code + - bank_code + - cs_count + - cs_total + - cs_warning + - cv_count + - cv_total + - cv_warning + - cc_count + - cc_total + - cc_warning + - cd_count + - cd_total + - cd_warning + - discount_margin + - down_rate + - regular_titles + - loan_limit + - loan_balance + - loan_fine + - registry + +occurrence: + '06': Liquidação efetuada em qualquer agência do BANESE + '10': Título baixado sem liquidação a pedido do cliente ou por decurso de prazo + '16': Liquidação efetuada em outros bancos participantes do Sistema de Compensação diff --git a/config/return_file/config.json b/config/return_file/config.json deleted file mode 100644 index 2dce312..0000000 --- a/config/return_file/config.json +++ /dev/null @@ -1,593 +0,0 @@ -{ - "240": { - "matcher": { - "file_header": { - "pattern": "/^(\\d{3})(\\d{4})0 {9}(\\d{1})(\\d{14})(\\d{20})(\\d{5})(.)(\\d{12})(.)(.)(.{30})(.{30}) {10}2(\\d{8})(\\d{6})(\\d{6})(\\d{3})(\\d{5}) {20}( {20}) {29}$/", - "map": [ - "bank_code", - "lot", - "assignor_document_type", - "assignor_document", - "assignor_covenant", - "assignor_agency", - "assignor_agency_cd", - "assignor_account", - "assignor_account_cd", - "assignor_agency_account_cd", - "assignor_name", - "bank_name", - "record_date", - "record_time", - "file_sequence", - "file_layout", - "density", - "assignor_use" - ] - }, - "lot_header": { - "pattern": "/^(\\d{3})(\\d{4})1([RT])(\\d{2}) {2}(\\d{3}) (\\d{1})(\\d{15})(\\d{20})(\\d{5})(.)(\\d{12})(.)(.)(.{30})(.{40})(.{40})(\\d{8})(\\d{8})(\\d{8}) {33}$/", - "map": [ - "bank_code", - "lot", - "service_code", - "service_type", - "lot_layout", - "assignor_document_type", - "assignor_document", - "assignor_covenant", - "assignor_agency", - "assignor_agency_cd", - "assignor_account", - "assignor_account_cd", - "assignor_agency_account_cd", - "assignor_name", - "message1", - "message2", - "shipping_return_number", - "shipping_return_record_date", - "credit_date" - ] - }, - "title_t": { - "pattern": "/^(\\d{3})(\\d{4})3(\\d{5})T (\\d{2})(\\d{5})(.)(\\d{12})(.)(.)(\\d{20})(\\d)(.{15})(\\d{8})(\\d{15})(\\d{3})(\\d{5})(\\d)(.{25})(\\d{2})(\\d)(\\d{15})(.{40})(\\d{10})(\\d{15})(.{10}) {17}$/", - "map": [ - "bank_code", - "lot", - "lot_registry", - "movement", - "assignor_agency", - "assignor_agency_cd", - "assignor_account", - "assignor_account_cd", - "assignor_account_cd", - "our_number", - "wallet", - "doc_number", - "due", - "value", - "receiver_bank", - "receiver_agency", - "receiver_agency_cd", - "assignor_use", - "specie", - "payer_document_type", - "payer_document", - "payer_name", - "contract", - "tax", - "occurrence" - ] - }, - "title_u": { - "pattern": "/^(\\d{3})(\\d{4})3(\\d{5})U (\\d{2})(\\d{15})(\\d{15})(\\d{15})(\\d{15})(\\d{15})(\\d{15})(\\d{15})(\\d{15})(\\d{8})(\\d{8})(.{4})(.{8})(\\d{15})(.{30})(\\d{3})(\\d{20}) {7}$/", - "map": [ - "bank_code", - "lot", - "lot_registry", - "movement", - "fine_value", - "discount_value", - "rebate", - "iof", - "value_paid", - "value_net", - "expenses", - "credits", - "occurrence_date", - "credit_date", - "payer_occurrence_code", - "payer_occurrence_date", - "payer_occurrence_value", - "payer_occurrence_detail", - "corresponding_bank", - "corresponding_bank_our_number" - ] - }, - "lot_trailer": { - "pattern": "/^(\\d{3})(\\d{4})5 {9}(\\d{6})(\\d{6})(\\d{17})(\\d{6})(\\d{17})(\\d{6})(\\d{17})(\\d{6})(\\d{17})(.{8}) {117}$/", - "map": [ - "bank_code", - "lot", - "lot_registry_count", - "title_cs_count", - "title_cs_total", - "title_cv_count", - "title_cv_total", - "title_cc_count", - "title_cc_total", - "title_cd_count", - "title_cd_total", - "warning" - ] - }, - "file_trailer": { - "pattern": "/^(\\d{3})(\\d{4})9 {9}(\\d{6})(\\d{6})(\\d{6}) {205}$/", - "map": [ - "bank_code", - "lot", - "lot_count", - "registry_count", - "reconciliation_accounts_count" - ] - } - }, - "fields": { - "movement": { - "error": { - "03": "Entrada Rejeitada", - "11": "Títulos em Carteira (Em Ser)", - "26": "Instrução Rejeitada", - "30": "Alteração de Dados Rejeitada", - "37": "Envio de e-mail/SMS rejeitado" - }, - "info": { - "02": "Entrada Confirmada", - "04": "Transferência de Carteira/Entrada", - "05": "Transferência de Carteira/Baixa", - "06": "Liquidação", - "07": "Confirmação do Recebimento da Instrução de Desconto", - "08": "Confirmação do Recebimento do Cancelamento do Desconto", - "09": "Baixa", - "12": "Confirmação Recebimento Instrução de Abatimento", - "13": "Confirmação Recebimento Instrução de Cancelamento Abatimento", - "14": "Confirmação Recebimento Instrução Alteração de Vencimento", - "15": "Franco de Pagamento", - "17": "Liquidação Após Baixa ou Liquidação Título Não Registrado", - "19": "Confirmação Recebimento Instrução de Protesto", - "20": "Confirmação Recebimento Instrução de Sustação/Cancelamento de Protesto", - "23": "Remessa a Cartório (Aponte em Cartório)", - "24": "Retirada de Cartório e Manutenção em Carteira", - "25": "Protestado e Baixado (Baixa por Ter Sido Protestado)", - "27": "Confirmação do Pedido de Alteração de Outros Dados", - "28": "Débito de Tarifas/Custas", - "29": "Ocorrências do Pagador", - "33": "Confirmação da Alteração dos Dados do Rateio de Crédito", - "34": "Confirmação do Cancelamento dos Dados do Rateio de Crédito", - "35": "Confirmação do Desagendamento do Débito Automático", - "36": "Confirmação de envio de e-mail/SMS", - "38": "Confirmação de alteração do Prazo Limite de Recebimento (a data deve ser informada no campo 28.3.p)", - "39": "Confirmação de Dispensa de Prazo Limite de Recebimento", - "40": "Confirmação da alteração do número do título dado pelo Beneficiário", - "41": "Confirmação da alteração do número controle do Participante", - "42": "Confirmação da alteração dos dados do Pagador", - "43": "Confirmação da alteração dos dados do Sacador/Avalista", - "44": "Título pago com cheque devolvido", - "45": "Título pago com cheque compensado", - "46": "Instrução para cancelar protesto confirmada", - "47": "Instrução para protesto para fins falimentares confirmada", - "48": "Confirmação de instrução de transferência de carteira/modalidade de cobrança", - "49": "Alteração de contrato de cobrança", - "50": "Título pago com cheque pendente de liquidação", - "51": "Título DDA reconhecido pelo Pagador", - "52": "Título DDA não reconhecido pelo Pagador", - "53": "Título DDA recusado pela CIP", - "54": "Confirmação da Instrução de Baixa de Título Negativado sem Protesto", - "55": "Confirmação de Pedido de Dispensa de Multa", - "56": "Confirmação do Pedido de Cobrança de Multa", - "57": "Confirmação do Pedido de Alteração de Cobrança de Juros", - "58": "Confirmação do Pedido de Alteração do Valor/Data de Desconto", - "59": "Confirmação do Pedido de Alteração do Beneficiário do Título", - "60": "Confirmação do Pedido de Dispensa de Juros de Mora", - "61": "Confirmação de Alteração do Valor Nominal do Título", - "63": "Título Sustado Judicialmente", - "64": "Confirmação de alteração do valor mínimo/percentual", - "65": "Confirmação de alteração do valor máximo/percentual" - } - }, - "occurrence": { - "A": { - "01": "Código do Banco Inválido", - "02": "Código do Registro Detalhe Inválido", - "03": "Código do Segmento Inválido", - "04": "Código de Movimento Não Permitido para Carteira", - "05": "Código de Movimento Inválido", - "06": "Tipo/Número de Inscrição do Beneficiário Inválidos", - "07": "Agência/Conta/DV Inválido", - "08": "Nosso Número Inválido", - "09": "Nosso Número Duplicado", - "10": "Carteira Inválida", - "11": "Forma de Cadastramento do Título Inválido", - "12": "Tipo de Documento Inválido", - "13": "Identificação da Emissão do Boleto de Pagamento Inválida", - "14": "Identificação da Distribuição do Boleto de Pagamento Inválida", - "15": "Características da Cobrança Incompatíveis", - "16": "Data de Vencimento Inválida", - "17": "Data de Vencimento Anterior a Data de Emissão", - "18": "Vencimento Fora do Prazo de Operação", - "19": "Título a Cargo de Bancos Correspondentes com Vencimento Inferior a XX Dias", - "20": "Valor do Título Inválido", - "21": "Espécie do Título Inválida", - "22": "Espécie do Título Não Permitida para a Carteira", - "23": "Aceite Inválido", - "24": "Data da Emissão Inválida", - "25": "Data da Emissão Posterior a Data de Entrada", - "26": "Código de Juros de Mora Inválido", - "27": "Valor/Taxa de Juros de Mora Inválido", - "28": "Código do Desconto Inválido", - "29": "Valor do Desconto Maior ou Igual ao Valor do Título", - "30": "Desconto a Conceder Não Confere", - "31": "Concessão de Desconto - Já Existe Desconto Anterior", - "32": "Valor do IOF Inválido", - "33": "Valor do Abatimento Inválido", - "34": "Valor do Abatimento Maior ou Igual ao Valor do Título", - "35": "Valor a Conceder Não Confere", - "36": "Concessão de Abatimento - Já Existe Abatimento Anterior", - "37": "Código para Protesto Inválido", - "38": "Prazo para Protesto Inválido", - "39": "Pedido de Protesto Não Permitido para o Título", - "40": "Título com Ordem de Protesto Emitida", - "41": "Pedido de Cancelamento/Sustação para Títulos sem Instrução de Protesto", - "42": "Código para Baixa/Devolução Inválido", - "43": "Prazo para Baixa/Devolução Inválido", - "44": "Código da Moeda Inválido", - "45": "Nome do Pagador Não Informado", - "46": "Tipo/Número de Inscrição do Pagador Inválidos", - "47": "Endereço do Pagador Não Informado", - "48": "CEP Inválido", - "49": "CEP Sem Praça de Cobrança (Não Localizado)", - "50": "CEP Referente a um Banco Correspondente", - "51": "CEP incompatível com a Unidade da Federação", - "52": "Unidade da Federação Inválida", - "53": "Tipo/Número de Inscrição do Sacador/Avalista Inválidos", - "54": "Sacador/Avalista Não Informado", - "55": "Nosso número no Banco Correspondente Não Informado", - "56": "Código do Banco Correspondente Não Informado", - "57": "Código da Multa Inválido", - "58": "Data da Multa Inválida", - "59": "Valor/Percentual da Multa Inválido", - "60": "Movimento para Título Não Cadastrado", - "61": "Alteração da Agência Cobradora/DV Inválida", - "62": "Tipo de Impressão Inválido", - "63": "Entrada para Título já cadastrado", - "64": "Número da Linha Inválido", - "65": "Código do Banco para Débito Inválido", - "66": "Agência/Conta/DV para Débito Inválido", - "67": "Dados para Débito incompatível com a Identificação da Emissão do Boleto de Pagamento", - "68": "Débito Automático Agendado", - "69": "Débito Não Agendado - Erro nos Dados da Remessa", - "70": "Débito Não Agendado - Pagador Não Consta do Cadastro de Autorizante", - "71": "Débito Não Agendado - Beneficiário Não Autorizado pelo Pagador", - "72": "Débito Não Agendado - Beneficiário Não Participa da Modalidade Débito Automático", - "73": "Débito Não Agendado - Código de Moeda Diferente de Real (R$)", - "74": "Débito Não Agendado - Data Vencimento Inválida", - "75": "Débito Não Agendado, conforme seu Pedido, Título Não Registrado", - "76": "Débito Não Agendado, Tipo/Num. Inscrição do Debitado, Inválido", - "77": "Transferência para Desconto Não Permitida para a Carteira do Título", - "78": "Data Inferior ou Igual ao Vencimento para Débito Automático", - "79": "Data Juros de Mora Inválido", - "80": "Data do Desconto Inválida", - "81": "Tentativas de Débito Esgotadas - Baixado", - "82": "Tentativas de Débito Esgotadas - Pendente", - "83": "Limite Excedido", - "84": "Número Autorização Inexistente", - "85": "Título com Pagamento Vinculado", - "86": "Seu Número Inválido", - "87": "e-mail/SMS enviado", - "88": "e-mail Lido", - "89": "e-mail/SMS devolvido - endereço de e-mail ou número do celular incorreto", - "90": "e-mail devolvido - caixa postal cheia", - "91": "e-mail/número do celular do Pagador não informado", - "92": "Pagador optante por Boleto de Pagamento Eletrônico - e-mail não enviado", - "93": "Código para emissão de Boleto de Pagamento não permite envio de e-mail", - "94": "Código da Carteira inválido para envio e-mail.", - "95": "Contrato não permite o envio de e-mail", - "96": "Número de contrato inválido", - "97": "Rejeição da alteração do prazo limite de recebimento (a data deve ser informada no campo 28.3.p)", - "98": "Rejeição de dispensa de prazo limite de recebimento", - "99": "Rejeição da alteração do número do título dado pelo Beneficiário", - "A1": "Rejeição da alteração do número controle do participante", - "A2": "Rejeição da alteração dos dados do Pagador", - "A3": "Rejeição da alteração dos dados do Sacador/avalista", - "A4": "Pagador DDA", - "A5": "Registro Rejeitado – Título já Liquidado", - "A6": "Código do Convenente Inválido ou Encerrado", - "A7": "Título já se encontra na situação Pretendida", - "A8": "Valor do Abatimento inválido para cancelamento", - "A9": "Não autoriza pagamento parcial", - "B1": "Autoriza recebimento parcial", - "B2": "Valor Nominal do Título Conflitante", - "B3": "Tipo de Pagamento Inválido", - "B4": "Valor Máximo/Percentual Inválido", - "B5": "Valor Mínimo/Percentual Inválido", - "ZY": "Já existe um movimento (Inclusão/Alteração/Baixa) em processamento para esse boleto na Base Centralizada", - "ZZ": "Existe mais de um tipo de movimento (Inclusão/Alteração/Baixa) para o mesmo boleto em um mesmo arquivo de remessa" - }, - "B": { - "01": "Tarifa de Extrato de Posição", - "02": "Tarifa de Manutenção de Título Vencido", - "03": "Tarifa de Sustação", - "04": "Tarifa de Protesto", - "05": "Tarifa de Outras Instruções", - "06": "Tarifa de Outras Ocorrências", - "07": "Tarifa de Envio de Duplicata ao Pagador", - "08": "Custas de Protesto", - "09": "Custas de Sustação de Protesto", - "10": "Custas de Cartório Distribuidor", - "11": "Custas de Edital", - "12": "Tarifa Sobre Devolução de Título Vencido", - "13": "Tarifa Sobre Registro Cobrada na Baixa/Liquidação", - "14": "Tarifa Sobre Reapresentação Automática", - "15": "Tarifa Sobre Rateio de Crédito", - "16": "Tarifa Sobre Informações Via Fax", - "17": "Tarifa Sobre Prorrogação de Vencimento", - "18": "Tarifa Sobre Alteração de Abatimento/Desconto", - "19": "Tarifa Sobre Arquivo mensal (Em Ser)", - "20": "Tarifa Sobre Emissão de Boleto de Pagamento Pré-Emitido pelo Banco" - }, - "C": { - "01": "Por Saldo", - "02": "Por Conta", - "03": "Liquidação no Guichê de Caixa em Dinheiro", - "04": "Compensação Eletrônica", - "05": "Compensação Convencional", - "06": "Por Meio Eletrônico", - "07": "Após Feriado Local", - "08": "Em Cartório", - "30": "Liquidação no Guichê de Caixa em Cheque", - "31": "Liquidação em banco correspondente", - "32": "Liquidação Terminal de Autoatendimento", - "33": "Liquidação na Internet (Home banking)", - "34": "Liquidado Office Banking", - "35": "Liquidado Correspondente em Dinheiro", - "36": "Liquidado Correspondente em Cheque", - "37": "Liquidado por meio de Central de Atendimento (Telefone)" - }, - "Baixa": { - "09": "Comandada Banco", - "10": "Comandada Cliente Arquivo", - "11": "Comandada Cliente On-line", - "12": "Decurso Prazo - Cliente", - "13": "Decurso Prazo - Banco", - "14": "Protestado", - "15": "Título Excluído Valor dos Juros / Multa / Encargo" - } - }, - "relation": { - "A": ["02", "03", "26", "30"], - "B": ["28"], - "C": ["06", "09", "17"] - } - } - }, - "400": { - "matcher": { - "file_header": { - "pattern": "/^02RETORNO(\\d{2}).{15}(\\d{20})(.{30})(\\d{3})(.{15})(\\d{6})(\\d{2})(\\d{14}) {273}(\\d{5})(\\d{6})$/", - "map": [ - "service_code", - "agency_account", - "assignor_name", - "bank_code", - "bank_name", - "record_date", - "assignor_document_type", - "assignor_document", - "file_sequence", - "registry" - ] - }, - "title": { - "pattern": "/^1(\\d{2})(\\d{14})(\\d{20})(.{25})(\\d{20}) {25}(\\d{1})(\\d{2})(\\d{6})(.{10}) {20}(\\d{6})(\\d{13})(\\d{3})(\\d{5})(\\d{2})(\\d{13})(\\d{13})(\\d{13})(\\d{13})(\\d{13})(\\d{13})(\\d{13})(\\d{13})(\\d{13}) {9}(.)(\\d{12})(\\d{6})(\\d{13})(\\d{2})(\\d{2}) {54}(\\d{2})(\\d)(\\d{6})$/", - "map": [ - "assignor_document_type", - "assignor_document", - "agency_account", - "assignor_use", - "our_number", - "wallet", - "occurrence", - "occurrence_date", - "your_number", - "due", - "value", - "receiver_bank", - "receiver_agency", - "kind", - "tax", - "expenses", - "fine_discount", - "iof", - "rebate", - "discount_value", - "value_paid", - "late_fine", - "credits", - "late_confirm", - "late_confirm_charge", - "discount_confirm", - "discount_value_confirm", - "instruction1_confirm", - "instruction2_confirm", - "protest_confirm", - "specie", - "registry" - ] - }, - "file_trailer": { - "pattern": "/^92(\\d{2})(\\d{3}) {10}(\\d{8})(\\d{14})(\\d{8}) {10}(\\d{8})(\\d{14})(\\d{8}) {10}(\\d{8})(\\d{14})(\\d{8}) {10}(\\d{8})(\\d{14})(\\d{8}) {10}(\\d{14})(\\d{3})(\\d{14})(\\d{14})(\\d{14})(\\d{14}) {144}(\\d{6})$/", - "map": [ - "service_code", - "bank_code", - "title_cs_count", - "title_cs_total", - "warning_cs", - "title_cv_count", - "title_cv_total", - "warning_cv", - "title_cc_count", - "title_cc_total", - "warning_cc", - "title_cd_count", - "title_cd_total", - "warning_cd", - "discount_margin", - "down_rate", - "regular_titles", - "loan_limit", - "loan_balance", - "loan_fine", - "registry" - ] - } - }, - "fields": { - "occurrence": { - "02": "Entrada Confirmada", - "04": "Alteração", - "06": "Liquidação Normal", - "07": "Pagamento por Conta", - "08": "Pagamento por Cartório", - "09": "Baixa Simples", - "10": "Devolvido / Protestado", - "11": "Em ser", - "12": "Abatimento Concedido", - "13": "Abatimento Cancelado", - "14": "Vencimento Alterado", - "15": "Baixa Automática", - "18": "Alteração Depositária", - "19": "Confirmação de Protesto", - "20": "Confirmação de Sustar Protesto", - "21": "Alteração Informações de Controle Empresa", - "22": "Alteração \"Seu Número\"", - "51": "Entrada Rejeitada" - }, - "error_table": [ - "Falta valor do IOC.", - "Não permite desconto/ abatimento.", - "Código do serviço inválido.", - "Novo vencimento igual/ menor que o da entrada.", - "Novo vencimento igual ao do Título.", - "Espécie Documento Inválida.", - "Espécie Documento Inexistente.", - "Tipo Operação Inválida.", - "Tipo Operação Inexistente.", - "Contrato Proibido para esta Carteira.", - "Falta Número do Contrato.", - "Proibido Informar Tipo de Conta.", - "Tipo de Conta do Contrato Inexistente.", - "Dígito de Contrato não confere.", - "Contrato Inexistente.", - "Data de Emissão Inválida.", - "Falta Valor do Título.", - "Vencimento Inválido.", - "Data Vencimento Anterior a Emissão.", - "Falta Vencimento Desconto.", - "Data Desconto Inválida.", - "Data Desconto Posterior ao Vencimento.", - "Falta Valor Desconto.", - "Falta Mora-1-Dia.", - "Banco/Agência Cobrador Inexistente.", - "BCO/AGE Cobrador não Cadastrado.", - "Código Pessoa Inválido.", - "Falta CEP, Banco e Agência Cobrador.", - "Falta Nome Sacado.", - "Falta Endereço.", - "Falta Cidade.", - "Falta Estado.", - "Estado Inválido.", - "Falta CPF/ CGC do Sacado.", - "Falta numeração - Bloquete emitido.", - "Título Pré-Numerado já Existente.", - "Dígito do Título Não Confere.", - "Proibido Protestar.", - "Proibido título pré-numerado p/ Correspondente.", - "Dígito Cliente/ Contrato com Erro.", - "Dígito Nosso Número com Erro.", - "Título Inexistente.", - "Título Liquidado.", - "Título Não Pode Ser Baixado.", - "Valor Nominal Incorreto.", - "Proibido Taxa – Multa p/ Correspondente.", - "Falta Tipo de Conta do Contrato.", - "Tipo de Conta Inexistente.", - "Dígito Contrato Não Confere.", - "Dígito do Título Não Confere.", - "Título Inexistente ou Liquidado.", - "Valor Abatimento Inválido.", - "Data Vencimento Inválida.", - "Estado Inválido.", - "Falta Tipo de Pessoa P/ Alteração de CGC/ CPF.", - "CPF/ CGC com Erro.", - "Data Emissão Inválida.", - "Data Vencimento Desconto Inválida.", - "Aceite Inválido para Espécie Documento.", - "Não Aceite Inválido para Espécie Documento.", - "Banco/ Agência Cobrador Inválido.", - "Limite Operacional Não Cadastrado.", - "Título já em situação de protesto.", - "Proibido alterar vencimento título descontado.", - "Proibido informar nosso número p/ cod. carteira.", - "Falta vencimento desconto-2.", - "Data desconto-2 inválida.", - "Data desconto-2 posterior ao vencimento.", - "Falta valor desconto-2.", - "Data vencimento desconto-2 inválida.", - "IOC maior que valor do título.", - "CEP não pertence ao Estado.", - "Seu número já existente.", - "Moeda Inválida para o tipo de Operação.", - "Moeda inexistente.", - "Nosso número/ dígito com erro.", - "Dias vencidos superior ao prazo de devolução.", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO", - "VAGO" - ] - } - } -} diff --git a/config/return_file/config_047.json b/config/return_file/config_047.json deleted file mode 100644 index 6f983e3..0000000 --- a/config/return_file/config_047.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "400": { - "fields": { - "occurrence": { - "06": "Liquidação efetuada em qualquer agência do BANESE.", - "10": "Título baixado sem liquidação a pedido do cliente ou por decurso de prazo.", - "16": "Liquidação efetuada em outros bancos participantes do Sistema de Compensação" - }, - "error_table": null - } - } -} diff --git a/config/router.yml b/config/router.yml new file mode 100644 index 0000000..2b0130b --- /dev/null +++ b/config/router.yml @@ -0,0 +1,87 @@ +configurations: + authentication: + secret: /etc/medools-router/private/bank-interchange + claims: + aud: bank-interchange + sub: BankInterchange API + extensions: + pdf: application/pdf + rem: text/plain + zip: application/zip + +resources: + assignments: + model: aryelgois\BankInterchange\Models\Assignment + + assignors: + model: aryelgois\BankInterchange\Models\Assignor + + banks: + model: aryelgois\BankInterchange\Models\Bank + methods: [GET, HEAD] + public: true + + clients: + model: aryelgois\BankInterchange\Models\Client + + currencies: + model: aryelgois\BankInterchange\Models\Currency + methods: [GET, HEAD] + public: true + + currency_codes: + model: aryelgois\BankInterchange\Models\CurrencyCode + methods: [GET, HEAD] + public: true + + document_kinds: + model: aryelgois\BankInterchange\Models\DocumentKind + methods: [GET, HEAD] + public: true + + full_addresses: + model: aryelgois\BankInterchange\Models\FullAddress + + people: + model: aryelgois\BankInterchange\Models\Person + + shipping_files: + model: aryelgois\BankInterchange\Models\ShippingFile + handlers: + GET: + text/plain: + resource: aryelgois\BankInterchange\ShippingFile\Controller::fromResource + application/zip: aryelgois\BankInterchange\ShippingFile\Controller::fromResource + + shipping_file_movements: + model: aryelgois\BankInterchange\Models\ShippingFileMovements + methods: [GET, HEAD] + public: true + + titles: + model: aryelgois\BankInterchange\Models\Title + handlers: + GET: + application/pdf: + resource: aryelgois\BankInterchange\BankBillet\Controller::fromResource + application/zip: aryelgois\BankInterchange\BankBillet\Controller::fromResource + + wallets: + model: aryelgois\BankInterchange\Models\Wallet + methods: [GET, HEAD] + public: true + + counties: + model: aryelgois\Databases\Models\Address\County + methods: [GET, HEAD] + public: true + + countries: + model: aryelgois\Databases\Models\Address\Country + methods: [GET, HEAD] + public: true + + states: + model: aryelgois\Databases\Models\Address\State + methods: [GET, HEAD] + public: true diff --git a/data/bank_interchange.yml b/data/bank_interchange.yml new file mode 100644 index 0000000..331c8d2 --- /dev/null +++ b/data/bank_interchange.yml @@ -0,0 +1,167 @@ +database: + name: bank_interchange + project: aryelgois/bank-interchange + license: MIT + +definitions: + pk: int PRIMARY KEY + pk_auto: pk AUTO_INCREMENT + tiny_pk: tinyint PRIMARY KEY + tiny_pk_auto: tiny_pk AUTO_INCREMENT + name: varchar(60) + money: decimal(17,4) + money_small: decimal(6,4) + cnab: enum('240', '400') + insertstamp: timestamp DEFAULT CURRENT_TIMESTAMP + +tables: + + # Helper Tables + + full_addresses: + id: pk_auto + county: int # -> address counties.id + neighborhood: name + place: name + number: varchar(20) + zipcode: varchar(8) + detail: name DEFAULT '' + update: timestamp + + people: + id: pk_auto + name: name + document: varchar(14) + + currencies: + id: tiny_pk_auto + symbol: varchar(5) + name: varchar(30) + name_plural: varchar(30) NULLABLE + decimals: tinyint DEFAULT 2 + thousand: char(1) + decimal: char(1) + + currency_codes: + currency: tinyint -> currencies.id + bank: int -> banks.id + billet: char(1) + cnab240: char(2) + cnab400: char(1) + + document_kinds: + id: tiny_pk_auto + bank: int -> banks.id + cnab: cnab + code: char(2) + symbol: varchar(3) + name: name + + shipping_file_movements: + id: tiny_pk_auto + bank: int -> banks.id + cnab: cnab + code: char(2) + name: name + + wallets: + id: tiny_pk_auto + bank: int -> banks.id + cnab: cnab + code: varchar(2) # used in cnab + operation: varchar(2) # used in billet barcode + symbol: char(2) + name: name + + # Main Tables + + assignors: + person: pk -> people.id + url: varchar(30) NULLABLE # Embeded into assignor's logo, in the Bank Billet + + assignments: + id: pk_auto + assignor: int -> assignors.person + address: int -> full_addresses.id + bank: int -> banks.id + document_kind: tinyint -> document_kinds.id + wallet: tinyint -> wallets.id + cnab: cnab + covenant: varchar(20) + agency: varchar(5) + agency_cd: varchar(2) + account: varchar(12) + account_cd: char(1) + agency_account_cd: char(1) + edi: varchar(6) + + banks: + id: pk_auto + code: char(3) UNIQUE # Defined by FEBRABAN + name: name + billet_tax: money_small + + clients: + id: pk_auto + assignor: int -> assignors.person + person: int -> people.id + address: int -> full_addresses.id + + titles: + id: pk_auto + shipping_file: int -> shipping_files.id KEY NULLABLE + movement: tinyint -> shipping_file_movements.id NULLABLE + assignment: int -> assignments.id KEY + client: int -> clients.id KEY + guarantor: int -> clients.id NULLABLE + currency: tinyint -> currencies.id + kind: tinyint -> document_kinds.id + doc_number: int + our_number: int + accept: enum('A', 'N') DEFAULT 'N' + value: money + value_paid: money DEFAULT 0 + ioc_iof: money + rebate: money + tax_value: money_small + tax_included: tinyint(1) + fine_type: tinyint DEFAULT 0 + fine_date: date NULLABLE + fine_value: money NULLABLE + interest_type: tinyint DEFAULT 3 + interest_date: date NULLABLE + interest_value: money NULLABLE + discount1_type: tinyint DEFAULT 0 + discount1_date: date NULLABLE + discount1_value: money NULLABLE + discount2_type: tinyint DEFAULT 0 + discount2_date: date NULLABLE + discount2_value: money NULLABLE + discount3_type: tinyint DEFAULT 0 + discount3_date: date NULLABLE + discount3_value: money NULLABLE + protest_code: tinyint NULLABLE + protest_days: tinyint NULLABLE + description: varchar(25) DEFAULT '' + occurrence: varchar(400) NULLABLE + occurrence_date: date NULLABLE + emission: date + due: date # Between 1997-10-07 and 2025-02-21, inclusive + update: timestamp + stamp: insertstamp + + shipping_files: + id: pk_auto + assignment: int -> assignments.id + counter: int + notes: varchar(20) DEFAULT '' + update: timestamp + stamp: insertstamp + +composite: + - PRIMARY currency_codes currency bank + - UNIQUE shipping_file_movements bank cnab code + - UNIQUE clients assignor person + - UNIQUE titles shipping_file assignment doc_number + - UNIQUE titles shipping_file assignment our_number + - UNIQUE shipping_files assignment counter diff --git a/data/bank_interchange_populate.sql b/data/bank_interchange_populate.sql new file mode 100644 index 0000000..6be8fea --- /dev/null +++ b/data/bank_interchange_populate.sql @@ -0,0 +1,114 @@ +SET NAMES 'utf8' COLLATE 'utf8_general_ci'; + + +-- Banks +-- TODO verify `billet_tax` + +INSERT INTO `banks` (`id`, `code`, `name`, `billet_tax`) VALUES +(1, '104', 'Caixa Econômica Federal', '2.0000'), +(2, '047', 'Banese', '2.0000'), +(3, '004', 'Banco do Nordeste', '2.0000'); + + +-- Currencies +-- TODO verify `currency_codes` + +INSERT INTO `currencies` (`id`, `symbol`, `name`, `name_plural`, `decimals`, `thousand`, `decimal`) VALUES +(1, 'R$', 'Real', 'Reais', 2, '', ','); + +INSERT INTO `currency_codes` (`currency`, `bank`, `billet`, `cnab240`, `cnab400`) VALUES +(1, 1, '9', '09', '?'), +(1, 2, '9', '09', '1'), +(1, 3, '9', '??', '0'); + + +-- Document kinds + +INSERT INTO `document_kinds` (`bank`, `cnab`, `code`, `symbol`, `name`) VALUES +(2, '240', '02', 'DM', 'Duplicata Mercantil'), +(2, '240', '04', 'DS', 'Duplicata de Serviço'), +(2, '240', '08', 'NCC', 'Nota de Crédito Comercial'), +(2, '240', '09', 'NCE', 'Nota de Crédito a Exportação'), +(2, '240', '10', 'NCI', 'Nota de Crédito Industrial'), +(2, '240', '11', 'NCR', 'Nota de Crédito Rural'), +(2, '240', '12', 'NP', 'Nota Promissória'), +(2, '240', '17', 'RC', 'Recibo'), +(2, '240', '20', 'AP', 'Apólice de Seguro'), +(2, '240', '21', 'ME', 'Mensalidade Escolar'), +(2, '240', '22', 'PC', 'Parcela de Consórcio'), +(2, '240', '23', 'NF', 'Nota Fiscal'), +(2, '240', '99', 'OU', 'Outros'), + +(2, '400', '01', 'DIB', 'Duplicata impressa pelo Banese'), -- -- +(2, '400', '02', 'NPB', 'NP impressa pelo Banese'), -- Dummy symbol, +(2, '400', '05', 'RCB', 'Recibo impresso pelo Banese'), -- not actually +(2, '400', '21', 'DIC', 'Duplicata impressa pelo cliente'), -- used by the +(2, '400', '22', 'NPC', 'NP impressa pelo cliente'), -- bank. +(2, '400', '25', 'RIC', 'Recibo impresso pelo cliente'), -- -- + +(3, '400', '01', 'DM', 'Duplicata Mercantil'), +(3, '400', '02', 'NP', 'Nota Promissória'), +(3, '400', '03', 'CH', 'Cheque'), +(3, '400', '04', 'CR', 'Carnê'), -- Dummy symbol +(3, '400', '05', 'RC', 'Recibo'), +(3, '400', '06', 'DS', 'Duplicata Prest. Serviços'), +(3, '400', '19', 'OU', 'Outros'); + + +-- Shipping File Movements +-- TODO check if missing for cnab 400 +-- NOTE commented entries are not implemented + +INSERT INTO `shipping_file_movements` (`bank`, `cnab`, `code`, `name`) VALUES +(2, '240', '01', 'Entrada de Títulos'), +(2, '240', '02', 'Pedido de Baixa'), +(2, '240', '04', 'Concessão de Abatimento'), +(2, '240', '05', 'Cancelamento de Abatimento'), +(2, '240', '06', 'Alteração de Vencimento'), +(2, '240', '07', 'Concessão de Desconto'), +(2, '240', '08', 'Cancelamento de Desconto'), +(2, '240', '12', 'Alteração de Juros de Mora'), +(2, '240', '13', 'Dispensar Cobrança de Juros de Mora'), +(2, '240', '14', 'Alteração de Valor/Percentual/Data de Multa'), +(2, '240', '15', 'Dispensar Cobrança de Multa'), +(2, '240', '16', 'Alteração de Valor/Data de Desconto'), +(2, '240', '18', 'Alteração do Valor de Abatimento'), +-- (2, '240', '19', 'Prazo Limite de Recebimento - Alterar'), +(2, '240', '21', 'Alterar número do título dado pelo cedente'), +(2, '240', '31', 'Alteração de Outros Dados'), +(2, '240', '42', 'Alteração de Espécie de Título'), + +(2, '400', '01', 'Entrada de Títulos'), + +(3, '400', '01', 'Entrada Normal'), +(3, '400', '02', 'Pedido de Baixa'), +(3, '400', '04', 'Concessão de Abatimento'), +(3, '400', '06', 'Alteração de Vencimento'), +(3, '400', '07', 'Alteração do Uso da empresa (Número de Controle)'), +(3, '400', '08', 'Alteração de Seu número'), +(3, '400', '09', 'Protestar'), +(3, '400', '10', 'Não Protestar'), +-- (3, '400', '12', 'Inclusão de Ocorrência'), +-- (3, '400', '13', 'Exclusão de Ocorrência'), +(3, '400', '31', 'Alteração de Outros Dados'); +-- (3, '400', '32', 'Pedido de Devolução'), +-- (3, '400', '33', 'Pedido de Devolução (entregue ao Sacado)'), +-- (3, '400', '99', 'Pedido dos Títulos em Aberto'); + + +-- Wallets +-- TODO verify data + +INSERT INTO `wallets` (`bank`, `cnab`, `code`, `operation`, `symbol`, `name`) VALUES +(1, '240', '1', '?', 'CR', 'Cobrança Registrada'), + +(2, '240', '1', '?', 'CS', 'Cobrança Simples'), + +(2, '400', '2', '?', 'CS', 'Cobrança Simples'), +(2, '400', '7', '?', 'CE', 'Cobrança Expressa'), + +(3, '400', '1', '21', 'CS', 'Cobrança Simples Escritural - Boleto Emitido Pelo Banco'), +(3, '400', '2', '41', 'CV', 'Cobrança Vinculada – Boleto Emitido Pelo Banco'), +(3, '400', '4', '21', 'CS', 'Cobrança Simples - Boleto Emitido Pelo Cliente'), +(3, '400', '5', '41', 'CV', 'Cobrança Vinculada - Boleto Emitido Pelo Cliente'), +(3, '400', 'I', '51', 'SR', 'Cobrança Simplificada (Sem Registro)'); diff --git a/data/bank_interchange_programs.sql b/data/bank_interchange_programs.sql new file mode 100644 index 0000000..c2dc73f --- /dev/null +++ b/data/bank_interchange_programs.sql @@ -0,0 +1,276 @@ +DELIMITER // + + +-- +-- Procedures +-- + + +CREATE PROCEDURE assignments_validate( + IN new_bank int, + IN new_document_kind int, + IN new_wallet int, + IN new_cnab enum('240', '400') +) READS SQL DATA +BEGIN + DECLARE kind_bank, wallet_bank int; + DECLARE kind_cnab, wallet_cnab enum('240', '400'); + + SELECT bank, cnab + INTO kind_bank, kind_cnab + FROM document_kinds WHERE id=new_document_kind; + + SELECT bank, cnab + INTO wallet_bank, wallet_cnab + FROM wallets WHERE id=new_wallet; + + IF new_bank <> kind_bank OR new_bank <> wallet_bank + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: bank, document_kind.bank and wallet.bank are different'; + END IF; + + IF new_cnab <> kind_cnab OR new_cnab <> wallet_cnab + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: cnab, document_kind.cnab and wallet.cnab are different'; + END IF; +END// + + +CREATE PROCEDURE titles_validate( + IN new_shipping_file int, + IN new_movement int, + IN new_assignment int, + IN new_client int, + IN new_guarantor int, + IN new_kind int, + IN new_emission date, + IN new_due date, + IN fine_type int, + INOUT fine_date date, + INOUT fine_value decimal, + IN interest_type int, + INOUT interest_date date, + INOUT interest_value decimal, + IN discount1_type int, + INOUT discount1_date date, + INOUT discount1_value decimal, + IN discount2_type int, + INOUT discount2_date date, + INOUT discount2_value decimal, + IN discount3_type int, + INOUT discount3_date date, + INOUT discount3_value decimal, + INOUT occurrence varchar(400), + INOUT occurrence_date date +) READS SQL DATA +BEGIN + DECLARE movement_bank, assignment_assignor, assignment_bank int; + DECLARE movement_cnab, assignment_cnab enum('240', '400'); + + SELECT assignor, bank, cnab + INTO assignment_assignor, assignment_bank, assignment_cnab + FROM assignments WHERE id=new_assignment; + + IF new_shipping_file IS NOT NULL + AND new_assignment <> (SELECT assignment FROM shipping_files WHERE id=new_shipping_file) + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: assignment and shipping_file.assignment are different'; + END IF; + + IF new_movement IS NOT NULL + THEN + SELECT bank, cnab + INTO movement_bank, movement_cnab + FROM shipping_file_movements WHERE id=new_movement; + + IF assignment_bank <> movement_bank + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: assignment.bank and movement.bank are different'; + END IF; + + IF assignment_cnab <> movement_cnab + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: assignment.cnab and movement.cnab are different'; + END IF; + END IF; + + IF assignment_assignor <> (SELECT assignor FROM clients WHERE id=new_client) + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: assignment.assignor and client.assignor are different'; + END IF; + + IF new_guarantor IS NOT NULL + AND assignment_assignor <> (SELECT assignor FROM clients WHERE id=new_guarantor) + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: assignment.assignor and guarantor.assignor are different'; + END IF; + + IF assignment_bank <> (SELECT bank FROM document_kinds WHERE id=new_kind) + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: assignment.bank and kind.bank are different'; + END IF; + + IF new_due < new_emission + THEN + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'Cannot add or update row: due date is earlier than emission'; + END IF; + + IF fine_type = 0 + THEN + SET fine_date = NULL; + SET fine_value = NULL; + END IF; + + IF interest_type = 3 + THEN + SET interest_date = NULL; + SET interest_value = NULL; + END IF; + + IF discount1_type = 0 + THEN + SET discount1_date = NULL; + SET discount1_value = NULL; + END IF; + + IF discount2_type = 0 + THEN + SET discount2_date = NULL; + SET discount2_value = NULL; + END IF; + + IF discount3_type = 0 + THEN + SET discount3_date = NULL; + SET discount3_value = NULL; + END IF; + + IF occurrence IS NULL OR occurrence_date IS NULL + THEN + SET occurrence = NULL; + SET occurrence_date = NULL; + END IF; +END// + + +-- +-- Triggers +-- + + +CREATE TRIGGER assignments_before_insert +BEFORE INSERT ON assignments FOR EACH ROW +CALL assignments_validate(NEW.bank, NEW.document_kind, NEW.wallet, NEW.cnab)// + + +CREATE TRIGGER assignments_before_update +BEFORE UPDATE ON assignments FOR EACH ROW +CALL assignments_validate(NEW.bank, NEW.document_kind, NEW.wallet, NEW.cnab)// + + +CREATE TRIGGER titles_before_insert +BEFORE INSERT ON titles FOR EACH ROW +BEGIN + DECLARE prev_doc_number, prev_our_number int; + + SELECT IFNULL(MAX(doc_number), 0), IFNULL(MAX(our_number), 0) + INTO prev_doc_number, prev_our_number + FROM titles WHERE assignment=NEW.assignment; + + IF NEW.doc_number IS NULL + THEN + SET NEW.doc_number = prev_doc_number + 1; + END IF; + + IF NEW.our_number IS NULL + THEN + SET NEW.our_number = prev_our_number + 1; + END IF; + + IF NEW.emission IS NULL + THEN + SET NEW.emission = NOW(); + END IF; + + CALL titles_validate( + NEW.shipping_file, + NEW.movement, + NEW.assignment, + NEW.client, + NEW.guarantor, + NEW.kind, + NEW.emission, + NEW.due, + NEW.fine_type, + NEW.fine_date, + NEW.fine_value, + NEW.interest_type, + NEW.interest_date, + NEW.interest_value, + NEW.discount1_type, + NEW.discount1_date, + NEW.discount1_value, + NEW.discount2_type, + NEW.discount2_date, + NEW.discount2_value, + NEW.discount3_type, + NEW.discount3_date, + NEW.discount3_value, + NEW.occurrence, + NEW.occurrence_date + ); +END// + + +CREATE TRIGGER titles_before_update +BEFORE UPDATE ON titles FOR EACH ROW +CALL titles_validate( + NEW.shipping_file, + NEW.movement, + NEW.assignment, + NEW.client, + NEW.guarantor, + NEW.kind, + NEW.emission, + NEW.due, + NEW.fine_type, + NEW.fine_date, + NEW.fine_value, + NEW.interest_type, + NEW.interest_date, + NEW.interest_value, + NEW.discount1_type, + NEW.discount1_date, + NEW.discount1_value, + NEW.discount2_type, + NEW.discount2_date, + NEW.discount2_value, + NEW.discount3_type, + NEW.discount3_date, + NEW.discount3_value, + NEW.occurrence, + NEW.occurrence_date +)// + + +CREATE TRIGGER shipping_files_before_insert +BEFORE INSERT ON shipping_files FOR EACH ROW +BEGIN + IF NEW.counter IS NULL + THEN + SET NEW.counter = (SELECT IFNULL(MAX(counter), 0) FROM shipping_files + WHERE assignment=NEW.assignment) + 1; + END IF; +END// + + +DELIMITER ; diff --git a/data/database.sql b/data/database.sql deleted file mode 100644 index f10d94c..0000000 --- a/data/database.sql +++ /dev/null @@ -1,140 +0,0 @@ -CREATE DATABASE bank_interchange CHARSET = UTF8 COLLATE = utf8_general_ci; -USE bank_interchange; - --- Basic Tables --- --- Low level Tables containing very basic data - -CREATE TABLE `full_addresses` ( - `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `county` int(10) UNSIGNED NOT NULL, -- Foreign: `address` `counties` (`id`) - `neighborhood` varchar(60) NOT NULL, - `place` varchar(60) NOT NULL, - `number` varchar(20) NOT NULL, - `zipcode` varchar(8) NOT NULL, - `detail` varchar(60) NOT NULL DEFAULT '', - `update` timestamp NOT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE `people` ( - `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `name` varchar(60) NOT NULL, - `document` varchar(14) NOT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE `species` ( - `id` tinyint(3) UNSIGNED NOT NULL AUTO_INCREMENT, - `symbol` varchar(5) NOT NULL, - `name` varchar(30) NOT NULL, - `name_plural` varchar(30), - `febraban` char(2) NOT NULL, - `decimals` tinyint(2) NOT NULL DEFAULT 2, - `thousand` char(1) NOT NULL, - `decimal` char(1) NOT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE `wallets` ( - `id` tinyint(3) UNSIGNED NOT NULL AUTO_INCREMENT, - `symbol` char(2) NOT NULL, - `name` varchar(60) NOT NULL, - `febraban` tinyint(2) NOT NULL, - `operation` tinyint(2) NOT NULL, - PRIMARY KEY (`id`) -); - --- Main Tables --- --- Tables with most important data for this package - -CREATE TABLE `banks` ( - `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `code` char(3) NOT NULL, - `name` varchar(60) NOT NULL, - `view` varchar(30) NOT NULL, - `logo` varchar(30), - `tax` decimal(6,4) NOT NULL, - PRIMARY KEY (`id`) -); - -CREATE TABLE `assignors` ( - `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `person` int(10) UNSIGNED NOT NULL, - `address` int(10) UNSIGNED NOT NULL, - `bank` int(10) UNSIGNED NOT NULL, - `wallet` tinyint(3) UNSIGNED NOT NULL, - `covenant` char(20) NOT NULL, - `agency` char(5) NOT NULL, - `agency_cd` char(1) NOT NULL, - `account` char(11) NOT NULL, - `account_cd` char(1) NOT NULL, - `edi` char(6) NOT NULL, - `logo` varchar(30), - `url` varchar(30), - PRIMARY KEY (`id`), - FOREIGN KEY (`person`) REFERENCES `people` (`id`), - FOREIGN KEY (`address`) REFERENCES `full_addresses` (`id`), - FOREIGN KEY (`bank`) REFERENCES `banks` (`id`), - FOREIGN KEY (`wallet`) REFERENCES `wallets` (`id`) -); - -CREATE TABLE `payers` ( - `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `person` int(10) UNSIGNED NOT NULL, - `address` int(10) UNSIGNED NOT NULL, - PRIMARY KEY (`id`), - FOREIGN KEY (`person`) REFERENCES `people` (`id`), - FOREIGN KEY (`address`) REFERENCES `full_addresses` (`id`) -); - -CREATE TABLE `titles` ( - `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `assignor` int(10) UNSIGNED NOT NULL, - `payer` int(10) UNSIGNED NOT NULL, - `guarantor` int(10) UNSIGNED, - `specie` tinyint(3) UNSIGNED NOT NULL, - `our_number` int(10) UNSIGNED NOT NULL, - `status` tinyint(1), - `doc_type` char(1) NOT NULL DEFAULT 1, - `kind` tinyint(2) UNSIGNED NOT NULL, - `value` decimal(17,4) NOT NULL, - `value_paid` decimal(17,4) NOT NULL DEFAULT 0, - `iof` decimal(17,4) NOT NULL, - `rebate` decimal(17,4) NOT NULL, - `fine_type` tinyint(1) NOT NULL DEFAULT 3, - `fine_date` date, - `fine_value` decimal(17,4), - `discount_type` tinyint(1) NOT NULL DEFAULT 3, - `discount_date` date, - `discount_value` decimal(17,4), - `description` varchar(25) NOT NULL, - `due` date NOT NULL, - `stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `update` datetime, - PRIMARY KEY (`id`), - FOREIGN KEY (`assignor`) REFERENCES `assignors` (`id`), - FOREIGN KEY (`payer`) REFERENCES `payers` (`id`), - FOREIGN KEY (`guarantor`) REFERENCES `payers` (`id`), - FOREIGN KEY (`specie`) REFERENCES `species` (`id`), - UNIQUE KEY `assignor__AND__our_number`(`assignor`, `our_number`) -); - -CREATE TABLE `shipping_files` ( - `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `assignor` int(10) UNSIGNED NOT NULL, - `status` tinyint(1), - `stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `update` datetime, - PRIMARY KEY (`id`), - FOREIGN KEY (`assignor`) REFERENCES `assignors` (`id`) -); - -CREATE TABLE `shipping_file_titles` ( - `shipping_file` int(10) UNSIGNED NOT NULL, - `title` int(10) UNSIGNED NOT NULL, - FOREIGN KEY (`shipping_file`) REFERENCES `shipping_files` (`id`), - FOREIGN KEY (`title`) REFERENCES `titles` (`id`), - PRIMARY KEY (`shipping_file`, `title`) -); diff --git a/data/database_populate.sql b/data/database_populate.sql deleted file mode 100644 index 6e6e9c0..0000000 --- a/data/database_populate.sql +++ /dev/null @@ -1,27 +0,0 @@ -USE bank_interchange; - --- Species - -INSERT INTO `species` (`id`, `symbol`, `name`, `name_plural`, `febraban`, `thousand`, `decimal`) VALUES -(1, 'R$', 'Real', 'Reais', 9, '', ','); - - --- Wallets --- @todo verify data - -INSERT INTO `wallets` (`id`, `febraban`, `operation`, `symbol`, `name`) VALUES -(1, 0, 51, 'SR', 'Sem Registro'), -(2, 1, 21, 'CS', 'Cobrança Simples'), -(3, 2, 41, 'CV', 'Cobrança Vinculada'); --- (4, 3, ??, 'CC', 'Cobrança Caucionada'), --- (5, 4, 00, 'CD', 'Cobrança Descontada'), --- (6, 5, 00, 'CV', 'Cobrança Vendor'); - - --- Banks --- @todo verify `tax` - -INSERT INTO `banks` (`id`, `code`, `name`, `view`, `logo`, `tax`) VALUES -(1, '104', 'Caixa Econômica Federal', 'CaixaEconomicaFederal', 'caixa.jpg', '2.0000'), -(2, '047', 'Banese', 'Banese', 'banese.jpg', '2.0000'), -(3, '004', 'B. do Nordeste', 'BancoDoNordeste', 'banco_do_nordeste.jpg', '2.0000'); diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..26489a3 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,383 @@ +# Documentation + +Index: + +- [API] + - [titles] + - [shipping_files] + - [return_file] + - [Authentication] +- [Server side] + - [Document Root] + - [Models] + - [Database] + - [Views] + - [BankBillet] + - [ShippingFile] + - [Controllers] + - [FilePack] + - [ReturnFile] + + +## API + +Requests made to `/api/` +are processed by [Medools Router][aryelgois/medools-router]. +There are some public resources +(read-only) +that you can `GET`, +but to access all resources +you need to [authenticate][Authentication]. +A list of available resources +can be found [here][router.yml]. + +There are a few resources +that deserve a deeper description: + + +#### titles + +A Title represents a transaction +between a client and an assignor +where a payment must be performed. +It might be for a product or service. + +It is the biggest resource +in this API, +and can be rendered as PDF. +When requesting multiple titles in this format, +they are grouped in a ZIP archive, +where each title is in its own PDF file. + + +#### shipping_files + +It groups one or more titles +in a CNAB document (`.REM`) +that can be sent to banks. +Just like `titles`, +multiple resources are grouped +in a ZIP archive. + +Additionally, +this resource supports +a special query parameter +`?with_billets` +that makes the shipping file's titles +to be included in the archive. + + +#### return_file + +Although it is not an actual resource, +this API endpoint allows a `POST` +with a return file in the payload +to be parsed. +By default, it also extracts +the most useful fields, +processes the occurrence message, +and tries to determine +`assignments` and `titles` ids. + +The client must send a `text/plain`, +while the server responds with either: + +- `application/x.bank-interchange.return-file_extracted+json` + + It has a predictable structure: + + ```json +{ + "return_file": { + "charging": { + "keys with charging data specific by bank, usually + 'cX_*' so you need to check when they exist": 0 + }, + "emission": "0000-00-00", + "sequence": 1 + }, + "titles": [ + { + "assignment": "1", + "id": "1", + "due": "0000-00-00", + "occurrence": "Usually a human readable message", + "occurrence_date": "0000-00-00", + "our_number": 1, + "tax": 0, + "value": 0, + "value_received": 0 + } + ] +} + ``` + + If the title's `assignment` or `id` + could not be detected, + or if you don't have the authorization, + they will be `null` + +- `application/x.bank-interchange.return-file_parsed+json` + + It is a way more nested JSON + with a structure dependent on + the CNAB layout and bank. + It tells the bank code, + CNAB and registries' type, + though. + +You can control +which representation will be sent +with an Accept header. + +Return files don't have a model +because they may +not always relate to +only one shipping file, +and may even +have titles from different shipping files. +Also, it can be incomplete +or require manual changes +before applying in the database. + +So, the solution: +the client sends the return file +they got from the bank, +this endpoint parses it +and returns useful fields, +the client reviews +and then may request the changes +to the appropriate resources. + + +### Authentication + +If you are registered, +send your `Basic` credentials +to `/api/auth/`. +It responds with a JWT token +that you need to send +in future requests +with a `Bearer` Authorization header. +If this token has expirated, +you need to re-authenticate. + +Your authentication token +may authorize access to +only some resources +(always including public ones), +as configured in the server side. + + +## Server side + +### Document Root + +`public/` should be the only directory +directly accessed by Apache +(or your web server of choice). + +It comes with a skeleton +to you start developing. + + +### Models + +Powered by [Medools][aryelgois/Medools], +the core models +are in the namespace `\aryelgois\BankInterchange\Models` + +Basically: + +- There are `banks` and `people` + + > it can be a physical person + > or a juridic person, + > i.e. any human or non-human entity + +- Some people have `assignments` with banks, + making them `assignors` + + > assignments are related to bank accounts + +- Some other people are `clients` of + those assignors + + > a person can be client of + > multiple assignors + +- Clients produce `titles` to + one of their assignor's + assignments + + > it could always be the same assignment + > or any of their assignments + +- Titles are grouped in `shipping_files` + to be sent to banks + +There are other models +that hold pieces of data, +to reduce repetition +and keep consistency. + +If a Title needs to be modified, +it has to be duplicated +in the database, +to allow the previous shipping file +to show the old Title's state, +while the new shipping file +contains a different data. +The `assignment` and `our_number` columns +must be equal. + + +#### Database + +It's schema +can be seen [here][bank_interchange.yml]. +There are some dependency databases +whose schema are in [here][address.yml] +and [here][authentication.yml]. + +They are defined in [YASQL][aryelgois/yasql], +a specification to write SQL in YAML. + +The core database +includes some programs +for data validation. + + +### Views + +Titles have a `BankBillet` view +with the layout specific +to assignment's bank, +while shipping files have +a `ShippingFile` view +specific to both assignment's CNAB and +bank. + +To support more banks +and CNAB layouts, +more views need to be added, +and they have to comply with +bank's specifications. + +> The reading of these specifications +> is tiring and their access is not that easy. +> Also, personally I find CNAB layouts inefficient +> and too repetitive. + + +#### BankBillet + +It supports +custom logo images +for banks and assignors, +located at +`assets/logos//.*`. +The accepted formats are +`JPG`, `JPEG`, `PNG` and `GIF`. + +Also, `config/billet.yml` +contains static data +included in all views. +Some fields support +a rudimentar template engine: +`header_info`, +`demonstrative`, +`instructions`. +It is based on `{{ }}` tags, +with context modifier (`$`) +and nested object (`->`) +support. + + +#### ShippingFile + +It basically +runs multiple `sprintf` +with hard-coded formats. +The data is also normalized. + +When using a custom +`movement`, +the registry fields are +masked out. + + +### Controllers + +To connect the resource's models +with these views +inside Medools Router, +an external handler is used +to control the process. + +It reads the resource request +and decides the specific view +to be used. + + +#### FilePack + +This class provides the ability +to combine multiple models +in a ZIP archive. + + +### ReturnFile + +This parser depends on +config files at +`config/return_file/cnab/.yml`. +It determines which file to use +based on the overall line length +and some characters in the first line. + +It can be best described as +a massive and recursive +try-and-error regex matcher. + +The Extractor _knows_ +which fields their related +return file has, +and its job is to group them +in a predictable structure. + +> It is sad that +> some banks simply send +> important fields empty +> when there is an error in +> the shipping file. +> Or worst: +> they are always empty +> (they could help to determine the title id) + + +[API]: #api +[titles]: #titles +[shipping_files]: #shipping_files +[return_file]: #return_file +[Authentication]: #authentication +[Server side]: #server-side +[Document Root]: #document-root +[Models]: #models +[Database]: #database +[Views]: #views +[BankBillet]: #bankbillet +[ShippingFile]: #shippingfile +[Controllers]: #controllers +[FilePack]: #filepack +[ReturnFile]: #returnfile + +[router.yml]: ../config/router.yml +[bank_interchange.yml]: ../data/bank_interchange.yml + +[aryelgois/medools]: https://github.com/aryelgois/Medools +[aryelgois/medools-router]: https://github.com/aryelgois/medools-router +[aryelgois/yasql]: https://github.com/aryelgois/yasql + +[address.yml]: https://github.com/aryelgois/databases/blob/master/data/address.yml +[authentication.yml]: https://github.com/aryelgois/medools-router/blob/master/data/authentication.yml diff --git a/example/actions/generate_billet.php b/example/actions/generate_billet.php deleted file mode 100644 index e527b4e..0000000 --- a/example/actions/generate_billet.php +++ /dev/null @@ -1,17 +0,0 @@ -output(); diff --git a/example/actions/generate_cnab.php b/example/actions/generate_cnab.php deleted file mode 100644 index 0823b72..0000000 --- a/example/actions/generate_cnab.php +++ /dev/null @@ -1,12 +0,0 @@ -output(); diff --git a/example/actions/generate_shipping_file.php b/example/actions/generate_shipping_file.php deleted file mode 100644 index a19a22e..0000000 --- a/example/actions/generate_shipping_file.php +++ /dev/null @@ -1,9 +0,0 @@ -setMultiple([ - 'name' => $_POST['name'], - 'document' => preg_replace('/[^\d]/', '', $_POST['document']), -]); - -if (!$person->save()) { - die('Error saving Person in the Database'); -} - -/* - * Create Address - */ -$address = new BankInterchange\Models\FullAddress; -$address->setMultiple([ - 'county' => $_POST['county'], - 'neighborhood' => $_POST['neighborhood'], - 'place' => $_POST['place'], - 'number' => $_POST['number'], - 'zipcode' => preg_replace('/[^\d]/', '', $_POST['zipcode']), - 'detail' => $_POST['detail'] ?? '', -]); - -if (!$address->save()) { - die('Error saving Address in the Database'); -} - -/* - * Create Assignor/Payer - */ -$is_assignor = $_POST['person_type'] == 'assignor'; -$model = ($is_assignor) - ? new BankInterchange\Models\Assignor - : new BankInterchange\Models\Payer; - -$model->person = $person; -$model->address = $address; - -if ($is_assignor) { - $model->setMultiple([ - 'bank' => $_POST['bank'], - 'wallet' => $_POST['wallet'], - 'covenant' => $_POST['covenant'], - 'agency' => $_POST['agency'], - 'agency_cd' => $_POST['agency_cd'], - 'account' => $_POST['account'], - 'account_cd' => $_POST['account_cd'], - 'edi' => $_POST['edi'], - 'logo' => $_POST['logo'] ?? null, - 'url' => $_POST['url'] ?? null, - ]); -} - -if (!$model->save()) { - die('Error saving ' . ($is_assignor ? 'Assignor' : 'Payer') . ' in the Database'); -} - -header('Location: ..'); diff --git a/example/actions/new_title.php b/example/actions/new_title.php deleted file mode 100644 index acc67b1..0000000 --- a/example/actions/new_title.php +++ /dev/null @@ -1,32 +0,0 @@ -getCurrentTimestamp(); -$due = date('Y-m-d', strtotime($due . ' + 30 days')); - -$title->setMultiple([ - 'assignor' => $_POST['assignor'], - 'payer' => $_POST['payer'], - 'specie' => 1, - 'doc_type' => 1, - 'kind' => 99, - 'value' => $_POST['value'], - 'iof' => 0, - 'rebate' => 0, - 'fine_type' => 3, - 'discount_type' => 3, - 'description' => 'Teste de Boleto', - 'due' => $due, -]); -$title->setOurNumber(); - -if (!$title->save()) { - die('Error saving Title in the Database'); -} - -header('Location: ..'); diff --git a/example/actions/process_return_file.php b/example/actions/process_return_file.php deleted file mode 100644 index 52a4087..0000000 --- a/example/actions/process_return_file.php +++ /dev/null @@ -1,139 +0,0 @@ -getMessages(); - -$tab = ' '; - -$template = $tab . " \n" - . str_repeat($tab . " %s\n", 9) - . $tab . " \n"; - -/* - * Result info - */ -$info = ''; -foreach ($messages['info'] as $m) { - $info .= sprintf( - $template, - $m['our_number'], - $m['due'], - $m['value'], - $m['value_paid'], - $m['receiver_bank'], - $m['receiver_agency'], - $m['movement'] ?? '', - $m['occurrence'], - $m['occurrence_date'] ?? '' - ); -} - -/* - * Errors - */ -$count = count($messages['error']); -$error_title = $count . ' errors' . ($count > 0 ? ':' : ''); -$error = ($count) - ? $tab . "
    \n" - . $tab . '
  • ' - . implode("
  • \n" . $tab . '
  • ', $messages['error']) - . "
  • \n" - . $tab . "
" - : ''; - -/* - * Warnings - */ -$count = count($messages['warning']); -$warning_title = $count . ' warnings' . ($count > 0 ? ':' : ''); -$warning = ($count) - ? $tab . "
    \n" - . $tab . '
  • ' - . implode("
  • \n" . $tab . '
  • ', $messages['warning']) - . "
  • \n" - . $tab . "
" - : ''; - -/* - * Applying - */ -if (isset($_POST['apply'])) { - $result = $return_file->apply(); - if ($result === false) { - $apply = 'Nothing to apply'; - } elseif ($result === true) { - $apply = 'Applied successfully'; - } else { - $apply = 'Titles which failed: ' . implode(', ', $result); - } -} - -?> - - - - - - - - -
-
-

Result:

- - - - - - - - - - - - - -
Our NumberDueValueValue paidReceiver BankReceiver AgencyMovementOcurrenceOcurrence date
- -

- - -

- - - - -

Applying...

-

- - -

- -
-
- - diff --git a/example/ajax/get_banks.php b/example/ajax/get_banks.php deleted file mode 100644 index a0ac239..0000000 --- a/example/ajax/get_banks.php +++ /dev/null @@ -1,23 +0,0 @@ - 'code', - ], - [ - 'id', - 'code', - 'name', - ] -); - -$result = ''; -foreach ($data as $bank) { - $result .= ''; -} -die($result); diff --git a/example/ajax/get_counties.php b/example/ajax/get_counties.php deleted file mode 100644 index 5413d6a..0000000 --- a/example/ajax/get_counties.php +++ /dev/null @@ -1,26 +0,0 @@ - $_GET['state'], - 'ORDER' => 'name', - ], - [ - 'id', - 'name', - ] -); - -$result = ''; -foreach ($data as $county) { - $result .= ''; -} -die($result); diff --git a/example/ajax/get_countries.php b/example/ajax/get_countries.php deleted file mode 100644 index 8c33664..0000000 --- a/example/ajax/get_countries.php +++ /dev/null @@ -1,25 +0,0 @@ - 'code_a2', - ], - [ - 'id', - 'code_a2', - 'name_en', - 'name_local', - ] -); - -$result = ''; -foreach ($data as $country) { - $result .= ''; -} -die($result); diff --git a/example/ajax/get_people.php b/example/ajax/get_people.php deleted file mode 100644 index adc4490..0000000 --- a/example/ajax/get_people.php +++ /dev/null @@ -1,21 +0,0 @@ -%s', - $model->id, - format_model_pretty($model, false) - ); -} -die($result); diff --git a/example/ajax/get_shipping_files.php b/example/ajax/get_shipping_files.php deleted file mode 100644 index 81058cf..0000000 --- a/example/ajax/get_shipping_files.php +++ /dev/null @@ -1,40 +0,0 @@ -' . str_repeat('%s', 5) . ''; - -$result = ''; -foreach ($iterator as $shipping_file) { - $id = $shipping_file->id; - $titles = []; - $total = 0.0; - - $shipping_file_titles = new aryelgois\Medools\ModelIterator( - 'aryelgois\\BankInterchange\\Models\\ShippingFileTitle', - ['shipping_file' => $id] - ); - foreach ($shipping_file_titles as $sft) { - $title = $sft->title; - $titles[] = $title->id; - $total += (float) $title->value; - } - - $links = 'CNAB240' - . 'CNAB400'; - - $data = [ - $id, - implode(', ', $titles), - $title->specie->format($total), - $shipping_file->stamp, - $links, - ]; - - $result .= sprintf($template, ...$data); -} -die($result); diff --git a/example/ajax/get_states.php b/example/ajax/get_states.php deleted file mode 100644 index d8df438..0000000 --- a/example/ajax/get_states.php +++ /dev/null @@ -1,28 +0,0 @@ - $_GET['country'], - 'ORDER' => 'code', - ], - [ - 'id', - 'code', - 'name', - ] -); - -$result = ''; -foreach ($data as $state) { - $result .= ''; -} -die($result); diff --git a/example/ajax/get_titles.php b/example/ajax/get_titles.php deleted file mode 100644 index cecfb81..0000000 --- a/example/ajax/get_titles.php +++ /dev/null @@ -1,32 +0,0 @@ -' . str_repeat('%s', 7) . ''; - -$already = array_column( - aryelgois\BankInterchange\Models\ShippingFileTitle::dump([], ['title']), - 'title' -); - -$result = ''; -foreach ($iterator as $model) { - $id = $model->id; - - $data = [ - (in_array($id, $already) ? '' : ''), - $id, - format_model_pretty($model->payer), - format_model_pretty($model->assignor), - $model->specie->format($model->value), - $model->stamp, - 'pdf', - ]; - - $result .= sprintf($template, ...$data); -} -die($result); diff --git a/example/ajax/get_wallets.php b/example/ajax/get_wallets.php deleted file mode 100644 index b447b44..0000000 --- a/example/ajax/get_wallets.php +++ /dev/null @@ -1,23 +0,0 @@ - 'febraban', - ], - [ - 'id', - 'symbol', - 'name', - ] -); - -$result = ''; -foreach ($data as $wallet) { - $result .= ''; -} -die($result); diff --git a/example/autoload.php b/example/autoload.php deleted file mode 100644 index ec3b45e..0000000 --- a/example/autoload.php +++ /dev/null @@ -1,34 +0,0 @@ -person; - $info = ($model instanceof BankInterchange\Models\Assignor) - ? 'Account: ' . $model->formatAgencyAccount(4, 11) - : $person->documentFormat(true); - - $result = $person->name - . ($html ? '
' : ' (') - . $info - . ($html ? '' : ')'); - - return $result; -} diff --git a/example/data/billet.json b/example/data/billet.json deleted file mode 100644 index 6b53b5c..0000000 --- a/example/data/billet.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "header_title": "Instruções de Impressão", - "header_body": "- Imprima em impressora jato de tinta (ink jet) ou laser em qualidade normal ou alta (Não use modo econômico).\n- Utilize folha A4 (210 x 297 mm) ou Carta (216 x 279 mm) e margens mínimas à esquerda e à direita do formulário.\n- Corte na linha indicada. Não rasure, risque, fure ou dobre a região onde se encontra o código de barras.\n- Caso não apareça o código de barras no final, clique em F5 para atualizar esta tela.\n- Caso tenha problemas ao imprimir, copie a seqüencia numérica abaixo e pague no caixa eletrônico ou no internet banking:", - "payment_place": "Pagável em qualquer Banco até o vencimento", - "demonstrative": "Pagamento de boleto - Teste de boleto\nTaxa bancária - {{ tax }}\nBankInterchange - https://www.github.com/aryelgois/bank-interchange", - "instructions": "- Sr. Caixa, cobrar multa de 2% após o vencimento\n- Receber até 10 dias após o vencimento\n- Em caso de dúvidas entre em contato conosco: suporte@exemplo.com.br\n Emitido pelo sistema BankInterchange - https://www.github.com/aryelgois/bank-interchange" -} diff --git a/example/index.php b/example/index.php deleted file mode 100644 index d7d40cd..0000000 --- a/example/index.php +++ /dev/null @@ -1,223 +0,0 @@ -(Select)'; - -?> - - - - Example - BankInterchange - - - - - - - - - - -
-
-

Intro

-

- BankInterchange is suitable to use in a e-Commerce. You just - need to adapt your website a little bit and provide some - operations for who is going to use it. -

-

- Explore this example source code to see how these basic - operations are implemented. Of course, you will want some data - validation to protect your customers. -

-
- -
-

Setup

-

- In order to use BankInterchange, you need the Database schema it - uses. -

-
    -
  1. - First, you need the Address Database provided - here. -
  2. -
  3. - Create this database in your - server, then populate it. -
  4. -
  5. - Configure the database options in ../config/medools.php -
  6. -
-
- -
-

New Person

-

- Add people to interact with the system. Your website should have - a customer register page, and the administrator would manage - the assignors. -

-
- - -
- - -
-
-
-
Name:

-
Document:
-
-
-

Address

-
Place:

-
Number:

-
Detail:

-
Neighborhood:

-
Zipcode:

-
Country:

-
State:

-
County:
-
-
-

Assignor

-
Bank:

-
Wallet:

-
Covenant:

-
Agency:

-
Agency check digit:

-
Account:

-
Account check digit:

-
EDI:

-
Logo:

-
URL:
-
-
- -
-
- -
-

New Title

-

- When the customer buys something, this is what is happening. -

-

- The client would log in, choose some products (the value below - is the sum) and the server would known the assignor. -

-
- - - - - - - - - - - - - - - - - -
The customer - -
is buying from - -
something of R$
-
-
- -
-

Generate Shipping File

-

- Below is a list of all titles in the Database. Choose which ones - will be in the Shipping File. Those previously sent do not have - a checkbox. -

-

- Note: Choose only Titles with the same assignor -

-
- - - - - - - - - - -
idClientAssignorValueDateBillet
-
- -

- Remember that, in production, you have to generate and send - the Shipping File before outputing the Billet. -

-
-
- -
-

Generate CNAB

-

- Below is a list of all Shipping Files in the database. Choose - how you want to render them. -

- - - - - - - - -
idTitlesTotalDateCNAB
-
- -
-

Return File

-

- Enter a Return File sent by a Bank to process it -

-
- -

- -

- -
-
-
- - - - diff --git a/example/main.css b/example/main.css deleted file mode 100644 index 271ebad..0000000 --- a/example/main.css +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Layout - */ - -body { - background: #f6f6f6; - font-family: sans-serif; - font-size: 14px; - line-height: 1.5em; - margin: 0; - min-width: 280px; -} - - -aside { - background: #e8e8e8; - position: fixed; - top: 0; - height: 100%; - width: 16em; - overflow-y: hidden; -} -aside:hover { -overflow-y: auto; -} - -aside header { - padding: 0.8em 0.2em 0.4em; - text-align: center; -} - -aside header h2 { - color: #444; - font-size: 1.5em; - margin: 0; -} - -aside header em { - color: #888; - display: block; - padding: 0.5em 0; -} - -aside > input { - display: none; -} - -aside > label { - display: none; - padding: 0.4em; -} - -aside nav a { - color: #0f94e9; - border-left: 0.3em solid transparent; - display: block; - padding: 0.6em; - text-decoration: none; -} -aside nav a:hover { - border-left-color: #549cda; -} -aside nav a.selected { - background: rgba(0, 0, 0, 0.1); - border-left-color: transparent; - color: #fff; - pointer-events: none; -} - - -main { - padding-left: 16em; -} - -main > section { - padding: 1em 6em 3em 6em; -} -main > section + section { - display: none; -} - -main > section > h2 { - color: #444; - font-size: 2.5em; - line-height: 1.2em; - margin: 0.6em 0 0.3em; -} - - -/* - * Responsive - */ - -@media screen and (max-width:800px) { - body { - font-size: 12px; - } - - aside { - position: static; - width: 100%; - } - - aside > label { - display: block; - text-align: center; - } - - aside nav { - display: none; - } - aside > input:checked + nav { - display: block; - } - - aside nav a { - display: inline-block; - width: 16em; - } - - main { - padding-left: 0; - } - - main > section { - padding: 1em 0.5em 1.5em 0.5em; - } -} - - -/* - * Elements - */ - -code { - background: rgba(255, 255, 255, 0.6); - border: 1px solid #ccc; - border-radius: 4px; - padding: 0.125em; -} - -fieldset { - border: none; - margin: 0; - padding: 0; -} - -form { - overflow: auto; -} -form.pretty { - background: #fbfbfb; - border: 1px solid #ddd; - padding: 0.25em; -} - -table.table-list { - background: #fff; - border-collapse: collapse; - border: 1px solid #e0e0e0; - width: 100%; -} - -table.table-list th { - background: #549cda; - color: #fff; -} - -table.table-list td, table.table-list th { - padding: 0.5em; -} - -table.table-list tr + tr td { - border-top: 1px solid #ddd; -} - -table.table-list tr:nth-child(odd) td { - background: #f0f0f0; -} - -textarea { - height: 200px; - width: 700px; - white-space: pre; -} - -#assignor_fields { - display: none; -} -#person_type_assignor:checked ~ #assignor_fields { - display: block; -} diff --git a/example/main.js b/example/main.js deleted file mode 100644 index 167b6c4..0000000 --- a/example/main.js +++ /dev/null @@ -1,80 +0,0 @@ -var main = { - aside_menu: function () { - var href = $(this).attr('href'); - if (href.startsWith('#')) { - $('body > main > section').hide().filter(href).show(); - } - - $(this).addClass('selected').siblings().removeClass('selected'); - }, - - ajax_get: function (selector, script, request, callback) { - $.get('/example/ajax/' + script, request, function (data) { - var el = $(selector); - - el.children(':not(.persistent)').remove(); - - el.append(data); - - if (typeof callback == 'function') { - callback.call(el); - } - }); - }, -}; - -$(document).ready(function () { - /* - * aside menu - */ - $('body > aside > nav').on('click', 'a', main.aside_menu) - .children('a[href="' + window.location.hash + '"]').click(); - - - /* - * ajax - */ - - $('#address_country').change(function () { - main.ajax_get( - '#address_state', - 'get_states.php', - {country: $(this).val()} - ); - }); - $('#address_state').change(function () { - main.ajax_get( - '#address_county', - 'get_counties.php', - {state: $(this).val()} - ); - }); - - main.ajax_get('#address_country', 'get_countries.php', null, function () { - this.change(); - }); - - main.ajax_get('#assignor_fields [name=bank]', 'get_banks.php'); - main.ajax_get('#assignor_fields [name=wallet]', 'get_wallets.php'); - - main.ajax_get('#payer_list', 'get_people.php', {'class': 'Payer'}); - main.ajax_get('#assignor_list', 'get_people.php', {'class': 'Assignor'}); - - main.ajax_get('#generate_shipping_file tbody', 'get_titles.php'); - main.ajax_get('#generate_cnab tbody', 'get_shipping_files.php'); - - /* - * New Person: toggle assignor fieldset - */ - $('#person_type_assignor, #person_type_customer').change(function () { - $('#assignor_fields').prop('disabled', $(this).val() != 'assignor'); - }).filter(':checked').change(); - - /* - * Generate Shipping File: check all titles available - */ - $('#check_all_titles').change(function () { - $(this).closest('tr').siblings().find('input[name="titles[]"]') - .prop('checked', $(this).is(':checked')); - }); -}); diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..915c0d1 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1 @@ +# SSLRequireSSL diff --git a/public/api/.htaccess b/public/api/.htaccess new file mode 100644 index 0000000..51c302d --- /dev/null +++ b/public/api/.htaccess @@ -0,0 +1,7 @@ +Options +FollowSymLinks +RewriteEngine On + +RewriteCond %{SCRIPT_FILENAME} !-d +RewriteCond %{SCRIPT_FILENAME} !-f + +RewriteRule ^.*$ index.php [L,QSA] diff --git a/public/api/auth/.htaccess b/public/api/auth/.htaccess new file mode 100644 index 0000000..38dcd05 --- /dev/null +++ b/public/api/auth/.htaccess @@ -0,0 +1 @@ +RewriteEngine Off diff --git a/public/api/auth/index.php b/public/api/auth/index.php new file mode 100644 index 0000000..f95f1cf --- /dev/null +++ b/public/api/auth/index.php @@ -0,0 +1,18 @@ +authenticate($request['headers']['Authorization'] ?? ''); diff --git a/public/api/index.php b/public/api/index.php new file mode 100644 index 0000000..d72c00f --- /dev/null +++ b/public/api/index.php @@ -0,0 +1,29 @@ +run( + $request['method'], + $request['uri'], + $request['headers'], + $request['body'] +); diff --git a/public/api/return_file/.htaccess b/public/api/return_file/.htaccess new file mode 100644 index 0000000..38dcd05 --- /dev/null +++ b/public/api/return_file/.htaccess @@ -0,0 +1 @@ +RewriteEngine Off diff --git a/public/api/return_file/index.php b/public/api/return_file/index.php new file mode 100644 index 0000000..7c1e13a --- /dev/null +++ b/public/api/return_file/index.php @@ -0,0 +1,27 @@ +run( + $request['method'], + '/', + $request['headers'], + $request['body'] +); diff --git a/public/bootstrap.php b/public/bootstrap.php new file mode 100644 index 0000000..63af4ea --- /dev/null +++ b/public/bootstrap.php @@ -0,0 +1,36 @@ + $_SERVER['REQUEST_METHOD'], + 'uri' => $uri, + 'headers' => getallheaders(), + 'body' => file_get_contents('php://input'), + 'url' => $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . $script_dir, + ]; +} diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..897aabb --- /dev/null +++ b/public/index.php @@ -0,0 +1,25 @@ + + + + + BankInterchange Example + + +

Hello World!

+

+ See the repository on GitHub. +

+

+ Access the API here. +

+ + + + + diff --git a/res/logos/assignors/brabecnet.png b/res/logos/assignors/brabecnet.png deleted file mode 100644 index 8d26510..0000000 Binary files a/res/logos/assignors/brabecnet.png and /dev/null differ diff --git a/res/logos/assignors/example.png b/res/logos/assignors/example.png deleted file mode 100644 index c9bedbe..0000000 Binary files a/res/logos/assignors/example.png and /dev/null differ diff --git a/src/BankBillet/Controller.php b/src/BankBillet/Controller.php new file mode 100644 index 0000000..a3df7a0 --- /dev/null +++ b/src/BankBillet/Controller.php @@ -0,0 +1,70 @@ +assignment; + + $view_class = __NAMESPACE__ . '\\Views\\' + . BankInterchange\Utils::toPascalCase($assignment->bank->name); + + return new $view_class($model, static::$data, static::$logos); + } +} diff --git a/src/BankBillet/View.php b/src/BankBillet/View.php new file mode 100644 index 0000000..7bb4b0c --- /dev/null +++ b/src/BankBillet/View.php @@ -0,0 +1,1077 @@ + ['Arial', 'B', 8, [0, 0, 0]], + 'digitable1' => ['Arial', 'B', 10, [0, 0, 0]], + 'billhead' => ['Arial', '', 6, [0, 0, 0]], + 'bank_code' => ['Arial', 'B', 14, [0, 0, 0]], + 'cell_title' => ['Arial', '', 6, [0, 0, 51]], + 'cell_data' => ['Arial', 'B', 7, [0, 0, 0]], + 'footer' => ['Arial', '', 9, [0, 0, 0]] + ]; + + /** + * Size of dashes: black, white + * + * @const integer[] + */ + const DASH_STYLE = [2, 1]; + + /** + * Default line width for borders + * + * @const numeric + */ + const DEFAULT_LINE_WIDTH = 0.2; + + /** + * Contains some data for the billet + * + * @var mixed[] + */ + protected $data = []; + + /** + * Dictionary of terms used in the billet + * + * @var string[] + */ + protected $dictionary = [ + 'accept' => 'Aceite', + 'addition' => '(+) Outros acréscimos', + 'agency_code' => 'Agência/Código do Beneficiário', + 'amount' => 'Quantidade', + 'assignor' => 'Beneficiário', + 'bank_use' => 'Uso do banco', + 'charged' => '(=) Valor cobrado', + 'client' => 'Pagador', + 'client_receipt' => 'Recibo do Pagador', + 'cod_down' => 'Cód. baixa', + 'compensation' => 'Ficha de Compensação', + 'cpf_cnpj' => 'CPF/CNPJ', + 'currency' => 'Espécie', + 'cut_here' => 'Corte na linha pontilhada', + 'date_document' => 'Data do documento', + 'date_due' => 'Vencimento', + 'date_process' => 'Data processameto', + 'deduction' => '(-) Outras deduções', + 'demonstrative' => 'Demonstrativo', + 'discount' => '(-) Desconto / Abatimentos', + 'doc_number' => 'Número do documento', + 'doc_number_sh' => 'Nº documento', + 'doc_value' => 'Valor documento', + 'doc_value=' => '(=) Valor documento', + 'doc_valueU' => 'Valor documento', + 'fine' => '(+) Mora / Multa', + 'guarantor' => 'Sacador/Avalista', + 'instructions' => 'Instruções (Texto de responsabilidade do beneficiário)', + 'kind' => 'Espécie doc.', + 'mech_auth' => 'Autenticação mecânica', + 'our_number' => 'Nosso número', + 'payment_place' => 'Local de pagamento', + 'wallet' => 'Carteira', + ]; + + /** + * Contains fields to be drawn in the billet + * + * @var array[] + */ + protected $fields = []; + + /** + * Paths to directories with logos + * + * @var string[] + */ + protected $logos = []; + + /** + * Holds data from database and manipulates some tables + * + * @var BankInterchange\Models\Title + */ + protected $title; + + /** + * Creates a new Billet View object + * + * @param Models\Title $title Holds data for the bank billet + * @param string[] $data Additional data for the bank billet + * @param string[] $logos Paths to directories with logos + */ + public function __construct( + BankInterchange\Models\Title $title, + array $data, + array $logos + ) { + $this->updateDictionary(); + $this->dictionary = array_map('utf8_decode', $this->dictionary); + + $this->title = $title; + + $value = $title->getActualValue(); + + $this->data = array_merge( + $data, + ['value' => $value], + $this->generateBarcode($value) + ); + + $this->fields = $this->generateFields(); + $this->logos = $logos; + + parent::__construct(); + $this->AliasNbPages('{{ total_pages }}'); + $this->SetLineWidth(static::DEFAULT_LINE_WIDTH); + $this->drawBillet(); + } + + /* + * FilePack\ViewInterface + * ========================================================================= + */ + + /** + * Generates a filename (without extension) + * + * @return string + */ + public function filename() + { + $title = $this->title; + return $title->assignment->id . '-' . $title->doc_number; + } + + /** + * Returns the View contents + * + * @return string + */ + public function getContents() + { + return $this->Output('S'); + } + + /** + * Outputs the View with appropriated headers + * + * @param string $filename File name to be outputed + */ + public function outputFile(string $filename) + { + $this->Output('I', $filename); + } + + /* + * Drawing + * ========================================================================= + */ + + /** + * Procedurally draws the bank billet using FPDF methods + */ + protected abstract function drawBillet(); + + /** + * Draws the Page Header + */ + protected function drawPageHeader() + { + $data = $this->data; + + $title = utf8_decode($data['header_title'] ?? ''); + $body = utf8_decode($data['header_body'] ?? ''); + $info = utf8_decode($this->simpleTemplate($data['header_info'] ?? '')); + + $this->billetSetFont('cell_data'); + + if (strlen($title)) { + $this->Cell(177, 3, $title, 0, 1, 'C'); + $this->Ln(2); + } + + if (strlen($body)) { + $this->MultiCell(177, 3, $body); + $this->Ln(2); + } + + $this->billetSetFont('digitable'); + $this->MultiCell(177, 3.5, $info); + $this->Ln(4); + } + + /** + * Draws the Billhead + */ + protected function drawBillhead() + { + $assignment = $this->title->assignment; + $assignor = $assignment->assignor; + $person = $assignor->person; + + $this->Ln(2); + + $logo = self::findFile("assignors/$person->id.*", $this->logos); + if ($logo !== null) { + $y = $this->GetY(); + $this->Image($logo, null, null, 40, 0, '', $assignor->url); + $y1 = $this->GetY(); + $this->SetXY(50, $y); + } + + $text = $person->name . "\n" + . $person->getFormattedDocument() . "\n" + . $assignment->address->outputLong(); + + $this->billetSetFont('billhead'); + $this->MultiCell(103.2, 2.5, utf8_decode($text)); + $this->SetY(max($y1 ?? 0, $this->GetY())); + } + + /** + * Inserts a dashed line, with optional text before or after + * + * The text uses previous font + * + * @param string $text An optional text + * @param boolean $text_first If the text comes first + * @param string $align Aligns the text (L|C|R) + */ + protected function drawDash($text = '', $text_first = false, $align = 'R') + { + $cell = function ($text, $align) { + $this->Cell(177, 4, $text, 0, 1, $align); + }; + + if ($text_first) { + $cell($text, $align); + } + + $this->SetLineWidth(static::DEFAULT_LINE_WIDTH * 0.625); + $y = $this->GetY(); + $this->SetDash(...static::DASH_STYLE); + $this->Line(10, $y, 187, $y); + $this->SetDash(); + $this->SetLineWidth(static::DEFAULT_LINE_WIDTH); + + if (!$text_first) { + $cell($text, $align); + } + } + + /** + * Inserts the Bank header + * + * @param string $digitable_align Aligns the digitable line (L|C|R) + * @param integer $line_width_factor Multiplier for the line width + */ + protected function drawBankHeader( + $digitable_align = 'R', + $line_width_factor = 2 + ) { + $bank = $this->title->assignment->bank; + $this->Ln(3); + + $logo = self::findFile("banks/$bank->id.*", $this->logos); + if ($logo !== null) { + $this->Image($logo, null, null, 40); + $this->SetXY(50, $this->GetY() - 7); + } else { + $this->billetSetFont('cell_data'); + $this->Cell(40, 7, utf8_decode($bank->name)); + } + + $this->SetLineWidth(static::DEFAULT_LINE_WIDTH * $line_width_factor); + $this->billetSetFont('bank_code'); + $this->Cell(15, 7, $this->formatBankCode(), 'LR', 0, 'C'); + $this->billetSetFont('digitable1'); + $this->Cell(122, 7, $this->data['digitable'], 0, 1, $digitable_align); + $y = $this->GetY(); + $this->Line(10, $y, 187, $y); + $this->SetLineWidth(static::DEFAULT_LINE_WIDTH); + } + + /** + * Produces a bar code from string of digits in style "2 of 5 intercalated" + * + * @param float $baseline Corresponds to the width of a wide bar + * @param float $height Bar height + */ + protected function drawBarCode($baseline = 0.8, $height = 13) + { + $data = $this->data['barcode']; + $wide = $baseline; + $narrow = $baseline / 3; + $map = [ + '00110', + '10001', + '01001', + '11000', + '00101', + '10100', + '01100', + '00011', + '10010', + '01010' + ]; + + // If odd $data + if ((strlen($data) % 2) != 0) { + $data = '0' . $data; + } + // Generate bits to be draw + $code = '0000'; // Leading value + for ($i = 0, $l = strlen($data); $i < $l; $i += 2) { + $code .= implode('', Utils\Utils::arrayInterpolate( + $map[$data[$i]], + $map[$data[$i + 1]] + )); + } + $code .= '100'; // Trailing value + + // Draw + $this->SetFillColor(0); + $x = $this->GetX(); + $y = $this->GetY(); + $draw = true; + foreach (str_split($code, 1) as $bit) { + $width = ($bit == '0' ? $narrow : $wide); + if ($draw) { + $this->Rect($x, $y, $width, $height, 'F'); + } + $x += $width; + $draw = !$draw; + } + $this->Ln($height); + } + + /** + * Draws a generic table + * + * Structure: + * + * ``` + * assignor | agency_code | currency | amount | our_number + * doc_number | cpf_cnpj | date_due | doc_value + * discount | deduction | fine | additions | charged + * client + * ``` + * + * @param mixed $border Applied on each row @see FPDF::Cell() border + * @param float[] $widths List of widths for each cell (total: 15 cells) + */ + protected function drawGenericTable1($border, array $widths) + { + $table = [ + [ + ['width' => $widths[0], 'field' => 'assignor'], + ['width' => $widths[1], 'field' => 'agency_code'], + ['width' => $widths[2], 'field' => 'currency'], + ['width' => $widths[3], 'field' => 'amount'], + ['width' => $widths[4], 'field' => 'our_number', 'align' => 'R'], + ], + [ + ['width' => $widths[5], 'field' => 'doc_number'], + ['width' => $widths[6], 'field' => 'cpf_cnpj'], + ['width' => $widths[7], 'field' => 'date_due'], + ['width' => $widths[8], 'field' => 'doc_value', 'align' => 'R'], + ], + [ + ['width' => $widths[9], 'field' => 'discount', 'align' => 'R'], + ['width' => $widths[10], 'field' => 'deduction', 'align' => 'R'], + ['width' => $widths[11], 'field' => 'fine', 'align' => 'R'], + ['width' => $widths[12], 'field' => 'addition', 'align' => 'R'], + ['width' => $widths[13], 'field' => 'charged', 'align' => 'R'], + ], + [ + ['width' => $widths[14], 'field' => 'client'], + ], + ]; + foreach ($table as $row) { + $this->drawRow($row, $border); + } + } + + /** + * Draws a generic table + * + * Structure: + * + * ``` + * payment_place | date_due + * assignor | agency_code + * date_document | doc_number_sh | kind | accept | date_process | our_number + * bank_use | wallet | currency | amount | doc_valueU | doc_value= + * demonstrative or instructions | discount + * | deduction + * | fine + * | additions + * | charged + * ``` + * + * @param string $big_cell Tells which information goes in the big cell + * Domain: 'demonstrative' or 'instructions' + * @param mixed $border Applied on each row @see FPDF::Cell() border + * @param float[] $widths List of widths for each cell (total: 18 cells) + */ + protected function drawGenericTable2( + string $big_cell, + $border, + array $widths + ) { + $table = [ + [ + ['width' => $widths[0], 'field' => 'payment_place'], + ['width' => $widths[1], 'field' => 'date_due', 'align' => 'R'], + ], + [ + ['width' => $widths[2], 'field' => 'assignor'], + ['width' => $widths[3], 'field' => 'agency_code', 'align' => 'R'], + ], + [ + ['width' => $widths[4], 'field' => 'date_document'], + ['width' => $widths[5], 'field' => 'doc_number_sh'], + ['width' => $widths[6], 'field' => 'kind'], + ['width' => $widths[7], 'field' => 'accept'], + ['width' => $widths[8], 'field' => 'date_process'], + ['width' => $widths[9], 'field' => 'our_number', 'align' => 'R'], + ], + [ + ['width' => $widths[10], 'field' => 'bank_use'], + ['width' => $widths[11], 'field' => 'wallet'], + ['width' => $widths[12], 'field' => 'currency'], + ['width' => $widths[13], 'field' => 'amount'], + ['width' => $widths[14], 'field' => 'doc_valueU'], + ['width' => $widths[15], 'field' => 'doc_value=', 'align' => 'R'], + ], + [ + ['width' => $widths[16], 'field' => $big_cell], + [ + 'width' => $widths[17], + 'field' => [ + 'discount', + 'deduction', + 'fine', + 'addition', + 'charged', + ], + 'align' => 'R' + ], + + ], + ]; + foreach ($table as $row) { + $this->drawRow($row, $border); + } + } + + /** + * Inserts row of cells + * + * Each cell has a field (or multiple fields) and a width. Each field is a + * key in $this->fields, and optionally tells its alignment. + * + * @param array[] $cells List of cells to bew draw + * @param mixed $row_border Row border @see FPDF::Cell() border + */ + protected function drawRow($cells, $row_border = 1) + { + $origin = ['x' => $this->GetX(), 'y' => $this->GetY()]; + $coords = []; + $x = $origin['x']; + foreach ($cells as $cell) { + $fields = (array) $cell['field']; + $count = count($fields); + $align = $cell['align'] ?? 'L'; + $width = $cell['width']; + foreach ($fields as $field) { + $border = (--$count > 0 ? 'B' : 0); + $field = $this->fields[$field]; + $title = $field['text'] ?? ''; + $data = $field['value'] ?? ''; + $this->billetSetFont('cell_title'); + $this->Cell($width, 3.5, $title, 0, 2); + $this->billetSetFont('cell_data'); + if (strpos($data, "\n")) { + $this->MultiCell($width, 3.5, $data, $border, $align); + } else { + $this->Cell($width, 3.5, $data, $border, 2, $align); + } + } + $x += $width; + $coords[] = ['x' => $x, 'y' => $this->GetY()]; + $this->SetXY($x, $origin['y']); + } + + $height = max(array_column($coords, 'y')) - $origin['y']; + $width = $coords[count($coords) - 1]['x'] - $origin['x']; + + for ($i = count($cells) - 2; $i >= 0; $i--) { + $x = $coords[$i]['x']; + $this->Line($x, $origin['y'], $x, $origin['y'] + $height); + } + + $this->SetXY($origin['x'], $origin['y']); + $this->Cell($width, $height, '', $row_border, 1); + } + + /* + * Formatting + * ========================================================================= + */ + + /** + * Formats Agency/Account + * + * @param boolean $symbol If shoud include symbols + * + * @return string + */ + protected function formatAgencyAccount($symbol = false) + { + return $this->title->assignment->formatAgencyAccount( + static::AGENCY_LENGTH, + static::ACCOUNT_LENGTH, + $symbol + ); + } + + /** + * Calculates Bank code's check digit and formats it + * + * @return string + */ + protected function formatBankCode() + { + $code = $this->title->assignment->bank->code; + + $checksum = Utils\Validation::mod11Pre($code); + $digit = $checksum * 10 % 11; + if ($digit == 10) { + $digit = 0; + } + + return $code . '-' . $digit; + } + + /** + * Formats a date from Y-m-d to d/m/Y + * + * @param string $date Date string + * + * @return string + */ + protected static function formatDate($date) + { + $d = \DateTime::createFromFormat('Y-m-d', $date); + if (!$d) { + $d = \DateTime::createFromFormat('Y-m-d H:i:s', $date); + } + return ($d ? $d->format('d/m/Y') : $date); + } + + /** + * Formats the barcode into a Digitable line + * + * @param string $bank Bank code (3 digits) + * @param string $currency Currency Code (1 digit) + * @param string $cd Check digit (1 digit) + * @param string $due Due Factor (4 digits) + * @param string $value Document Value (10 digits: 8 integers and 2 decimals) + * @param string $free Free space (25 digits, defined by each bank) + * @return string + */ + protected static function formatDigitable( + $bank_code, + $currency_code, + $check_digit, + $due_factor, + $value, + $free_space + ) { + $fields = []; + + /* + * Field #0 + * + * - $bank_code + * - $currency_code + * - 5 first digits from $free_space + * - Check digit for this field + */ + $tmp = $bank_code . $currency_code . substr($free_space, 0, 5); + $tmp .= Utils\Validation::mod10($tmp); + $fields[] = implode('.', str_split($tmp, 5)); + + /* + * Field #1 + * + * - Digits 6 to 15 from $free_space + * - Check digit for this field + */ + $tmp = substr($free_space, 5, 10); + $fields[] = implode('.', str_split($tmp, 5)) + . Utils\Validation::mod10($tmp); + + /* + * Field #2 + * + * - Digits 16 to 25 from $free_space + * - Check digit for this field + */ + $tmp = substr($free_space, 15, 10); + $fields[] = implode('.', str_split($tmp, 5)) + . Utils\Validation::mod10($tmp); + + /* + * Field #3 + * + * - Digitable line $check_digit + */ + $fields[] = $check_digit; + + /* + * Field #4 + * + * - $due_factor + * - Document $value + */ + $fields[] = $due_factor . $value; + + return implode(' ', $fields); + } + + /** + * Formats a numeric value as monetary value + * + * @param number $value Value to be formatted + * @param string $format @see Models/Currency::format() + * + * @return string + */ + protected function formatMoney($value, $format = 'symbol') + { + return $this->title->currency->format($value, $format); + } + + /** + * Calculates Our number's check digit and formats it + * + * @param boolean $mask If should add a dash between number and check digit + * + * @return string + */ + protected function formatOurNumber($mask = false) + { + $our_number = BankInterchange\Utils::padNumber( + $this->title->our_number, + static::OUR_NUMBER_LENGTH + ); + + return $our_number . ($mask ? '-' : '') . $this->checkDigitOurNumber(); + } + + /* + * Internal + * ========================================================================= + */ + + /** + * Allows an easy way to set current font + * + * @param string $font Key for FONTS + */ + protected function billetSetFont($font) + { + $f = static::FONTS[$font]; + $this->SetFont($f[0], $f[1], $f[2]); + if (count($f) > 3) { + $this->SetTextColor(...$f[3]); + } + } + + /** + * Calculates the check digit for Barcode + * + * @param string $code 43 digits + * @return string + */ + protected static function checkDigitBarcode($code) + { + $tmp = Utils\Validation::mod11($code); + + $cd = ($tmp == 0 || $tmp == 1 || $tmp == 10) + ? 1 + : 11 - $tmp; + + return $cd; + } + + /** + * Calculates Our number's check digit + * + * @return string + */ + protected function checkDigitOurNumber() + { + $title = $this->title; + $assignment = $title->assignment; + + $our_number = BankInterchange\Utils::padNumber($assignment->agency, 3) + . BankInterchange\Utils::padNumber($title->our_number, 8); + + return $title->checkDigitOurNumberAlgorithm($our_number); + } + + /** + * Calculate the amount of days since 1997-10-07 + * + * @return string Four digits + */ + protected function dueFactor() + { + $date = \DateTime::createFromFormat('Y-m-d', $this->title->due); + $epoch = new \DateTime('1997-10-07'); + if ($date && $date > $epoch) { + $diff = substr($date->diff($epoch)->format('%a'), -4); + return str_pad($diff, 4, '0', STR_PAD_LEFT); + } + return '0000'; + } + + /** + * Finds a file in a list of paths + * + * @param string $file glob pattern to search inside each path + * @param array $paths List of paths to search + * + * @return string If file was found + * @return null If file was not found + */ + protected static function findFile($file, array $paths) + { + if ($file === null) { + return null; + } + foreach ($paths as $path) { + $files = glob("$path/$file"); + if (!empty($files)) { + return $files[0]; + } + } + } + + /** + * Generates the barcode data and its digitable line + * + * @param numeric $value Billet value + * + * @return string[] + */ + protected function generateBarcode($value) + { + $title = $this->title; + + $value = $title->currency->format($value, 'nomask'); + $barcode = [ + $title->assignment->bank->code, + $title->getCurrencyCode()->billet, + '', // Check digit + $this->dueFactor(), + BankInterchange\Utils::padNumber($value, 10), + $this->generateFreeSpace() + ]; + $barcode[2] = self::checkDigitBarcode(implode('', $barcode)); + + return [ + 'barcode' => implode('', $barcode), + 'digitable' => self::formatDigitable(...$barcode), + ]; + } + + /** + * Generates fields to be drawn in the billet + * + * @return array[] + */ + protected function generateFields() + { + $data = $this->data; + $title = $this->title; + $assignment = $title->assignment; + $assignor_person = $title->assignment->assignor->person; + + $doc_number = BankInterchange\Utils::padNumber($title->doc_number, 10); + $value = $this->formatMoney($data['value']); + + $demonstrative = $this->simpleTemplate($data['demonstrative'] ?? ''); + $instructions = $this->simpleTemplate($data['instructions'] ?? ''); + + $guarantor = ($title->guarantor !== null) + ? $title->guarantor->person->name . ' ' + . $title->guarantor->address->outputShort() + : ''; + + $fields = [ + 'accept' => $title->accept, + 'addition' => $data['addition'] ?? '', + 'agency_code' => $this->formatAgencyAccount(true), + 'amount' => $data['amount'] ?? '', + 'assignor' => $assignor_person->name, + 'bank_use' => $data['bank_use'] ?? '', + 'charged' => $data['charged'] ?? '', + 'client' => $title->client->person->name, + 'cpf_cnpj' => $assignor_person->getFormattedDocument(), + 'currency' => $title->currency->symbol, + 'date_document' => self::formatDate($title->emission), + 'date_due' => self::formatDate($title->due), + 'date_process' => date('d/m/Y'), + 'deduction' => $data['deduction'] ?? '', + 'demonstrative' => trim($demonstrative), + 'discount' => $data['discount'] ?? '', + 'doc_number' => $doc_number, + 'doc_number_sh' => $doc_number, + 'doc_value' => $value, + 'doc_value=' => $value, + 'doc_valueU' => $data['doc_valueU'] ?? '', + 'fine' => $data['fine'] ?? '', + 'guarantor' => $guarantor, + 'instructions' => trim($instructions), + 'kind' => $title->kind->symbol, + 'our_number' => $this->formatOurNumber(true), + 'payment_place' => $data['payment_place'] ?? '', + 'wallet' => $assignment->wallet->symbol, + ]; + + $result = []; + foreach ($fields as $field_key => $field_value) { + $result[$field_key] = [ + 'text' => $this->dictionary[$field_key], + 'value' => utf8_decode($field_value), + ]; + } + return $result; + } + + /** + * Free space, defined by Bank. + * + * Here: Our number . Agency/Assignor + */ + protected function generateFreeSpace() + { + return $this->formatOurNumber() . $this->formatAgencyAccount(); + } + + /** + * Expands a template tag + * + * NOTE: + * - Some tags have specific formatting that is applied automatically + * - If the tag points to a Medools\Model, its primary key is returned + * + * @param string[] $match Match from preg_replace_callback() + * + * @return string + */ + protected function parseTemplateTag(array $match) + { + $keys = explode('->', $match[1]); + + $context = 'title'; + if ($keys[0][0] === '$') { + $context = substr(array_shift($keys), 1); + } + + $previous = null; + $model = $this->{$context}; + + foreach ($keys as $key) { + $previous = $model; + $model = (is_object($model)) + ? $model->{$key} + : $model[$key]; + } + + if ($context === 'dictionary') { + $model = utf8_encode($model); + } + + if ($model instanceof Medools\Model) { + return implode('-', $model->getPrimaryKey()); + } + + switch ($key) { + case 'billet_tax': + case 'discount1_value': + case 'discount2_value': + case 'discount3_value': + case 'fine_value': + case 'interest_value': + case 'ioc_iof': + case 'rebate': + case 'tax_value': + case 'value_paid': + case 'value': + $model = $this->formatMoney($model); + break; + + case 'discount1_date': + case 'discount2_date': + case 'discount3_type_date': + case 'due': + case 'emission': + case 'fine_date': + case 'interest_date': + $model = $this->formatDate($model); + break; + + case 'document': + $model = $previous->getFormattedDocument(); + break; + + case 'stamp': + case 'update': + $model = date('H:i:s d/m/Y', strtotime($model)); + break; + + case 'zipcode': + $model = Utils\Validation::cep($model); + break; + } + + return $model; + } + + /** + * Searches and replaces {{ key }} tags + * + * - By default tags access $title columns + * - Nested Models can be accessed with {{ key->key }} + * - A starting '$' allows a different context: {{ $data->key }} + * + * @param string $subject The string to search and replace + * + * @return string + */ + protected function simpleTemplate(string $subject) + { + if ($subject === '') { + return ''; + } + + $result = preg_replace_callback( + '/{{ ?(\$?\w+(?:->\w+)*) ?}}/', + [$this, 'parseTemplateTag'], + $subject + ); + + return $result; + } + + /* + * Hooks + * ========================================================================= + */ + + /** + * Modifies $dictionary before its UTF8 decoding + */ + protected function updateDictionary() + { + return; + } + + /* + * For FPDF + * ========================================================================= + */ + + /** + * This extension allows to set a dash pattern and draw dashed lines or rectangles. + * + * Call the function without parameter to restore normal drawing. + * + * @link http://www.fpdf.org/en/script/script33.php + * @author yukihiro_o + * @license FPDF + * @param float black Length of dashes + * @param float white Length of gaps + */ + protected function SetDash($black = null, $white = null) + { + if ($black !== null) { + $s = sprintf('[%.3F %.3F] 0 d', $black * $this->k, $white * $this->k); + } else { + $s = '[] 0 d'; + } + $this->_out($s); + } + + /** + * Page footer + * + * Used internally by FPDF + */ + public function Footer() + { + $this->SetY(-15); + $this->billetSetFont('footer'); + $this->Cell(88.5, 5, $this->PageNo() . " / $this->AliasNbPages"); + $this->Cell(88.5, 5, date('Y-m-d H:i:s O'), 0, 0, 'R'); + } +} diff --git a/src/BankBillet/Views/BancoDoNordeste.php b/src/BankBillet/Views/BancoDoNordeste.php new file mode 100644 index 0000000..732b40d --- /dev/null +++ b/src/BankBillet/Views/BancoDoNordeste.php @@ -0,0 +1,60 @@ +title; + $our_number = BankInterchange\Utils::padNumber($title->our_number, 7); + return $title->checkDigitOurNumberAlgorithm($our_number, 8); + } + + /** + * Free space, defined by Bank. + * + * Here: Agency/Assignor . Our number . Wallet operation . '000' + */ + protected function generateFreeSpace() + { + $result = $this->formatAgencyAccount() + . $this->formatOurNumber() + . $this->title->assignment->wallet->operation + . '000'; + return $result; + } + + /** + * Banco do Banese requires the wallet field to be the wallet operation + */ + protected function drawBillet() + { + $wallet = $this->title->assignment->wallet; + $this->fields['wallet']['value'] = $wallet->operation; + parent::drawBillet(); + } +} diff --git a/src/BankBillet/Views/Banese.php b/src/BankBillet/Views/Banese.php new file mode 100644 index 0000000..01f77c5 --- /dev/null +++ b/src/BankBillet/Views/Banese.php @@ -0,0 +1,205 @@ + ['Arial', 'B', 8, [ 0, 0, 0]], + 'digitable1' => ['Arial', 'B', 10, [ 0, 0, 0]], + 'billhead' => ['Arial', '', 6, [ 0, 0, 0]], + 'bank_code' => ['Arial', 'B', 9, [ 0, 0, 0]], + 'cell_title' => ['Arial', '', 6, [20, 20, 20]], + 'cell_data' => ['Arial', 'B', 7, [ 0, 0, 0]], + 'footer' => ['Arial', '', 9, [ 0, 0, 0]] + ]; + + const DASH_STYLE = [0.625, 0.75]; + + const DEFAULT_LINE_WIDTH = 0.3; + + /** + * Procedurally draws the bank billet using FPDF methods + */ + protected function drawBillet() + { + // Add document to assignor + $assignor = $this->title->assignment->assignor; + $this->fields['assignor']['value'] .= ' ' + . $assignor->person->getFormattedDocument(true); + + // Draw billet + $dict = $this->dictionary; + + $this->AddPage(); + + $this->drawPageHeader(); + + $this->billetSetFont('cell_data'); + $this->drawDash($dict['client_receipt']); + + $this->drawBillhead(); + + $this->drawTable('demonstrative'); + + $this->Ln(4); + + $this->billetSetFont('cell_title'); + $this->drawDash($dict['compensation']); + + $this->SetY($this->GetY() - 3); + + $this->drawTable('instructions'); + + $this->SetY($this->GetY() - 3); + $this->drawBarCode(); + } + + /** + * Extends drawGenericTable2() + * + * @param string $big_cell @see drawGenericTable2() + */ + protected function drawTable($big_cell) + { + $this->drawBankHeader('L', 1); + + $this->drawGenericTable2( + $big_cell, + 'LBR', + [ + 127.2, 49.8, + 127.2, 49.8, + 32 , 27 , 20, 12, 36.2, 49.8, + 32 , 16 , 11, 32, 36.2, 49.8, + 127.2, 49.8 + ] + ); + + $dict = $this->dictionary; + $fields = $this->fields; + $title = $this->title; + $client = $title->client; + $wallet = $title->assignment->wallet; + + // Client + $client_data = $fields['client']['value'] . "\n" + . utf8_decode($client->address->outputLong()); + $client_doc = $client->person->getFormattedDocument(true); + $y = $this->GetY(); + $this->billetSetFont('cell_title'); + $this->Cell(10, 3.5, $fields['client']['text']); + $this->SetXY($this->GetX() + 5, $y); + $this->billetSetFont('cell_data'); + $this->MultiCell(112.2, 3.5, $client_data); + $y1 = $this->GetY(); + $this->SetXY(119.2, $y); + $this->Cell(36, 3.5, $client_doc, 0, 0, 'C'); + $this->setY($y1); + + // Guarantor + $this->billetSetFont('cell_title'); + $this->Cell(24, 3.5, $fields['guarantor']['text']); + $this->billetSetFont('cell_data'); + $this->Cell(153, 3.5, $fields['guarantor']['value'], 0, 1); + $x = $this->GetX(); + $y1 = $this->GetY(); + + // Client / Guarantor border + $this->Line($x, $y, $x, $y1); + $this->Line($x, $y1, 187, $y1); + $this->Line(187, $y, 187, $y1); + + // Mechanical authentication + $text = $dict['mech_auth'] . '/' . utf8_decode($wallet->name); + $this->SetX(119.2); + $this->billetSetFont('cell_title'); + $this->Cell(67.8, 3.5, $text); + $this->Ln(3.5); + } + + /** + * Free space: Asbace key + * + * Here: Agency . Account . Our number . Bank code . 2 check digits + */ + protected function generateFreeSpace() + { + $assignment = $this->title->assignment; + $bank = $assignment->bank; + + $key = BankInterchange\Utils::padNumber($assignment->agency, 2, true) + . BankInterchange\Utils::padNumber($assignment->account, 9, true) + . $this->formatOurNumber() + . BankInterchange\Utils::padNumber($bank->code, 3, true); + $cd1 = Validation::mod10($key); + $cd2 = Validation::mod11($key . $cd1, 7); + + if ($cd2 === 1) { + if ($cd1 < 9) { + $cd1++; + $cd2 = Validation::mod11($key . $cd1, 7); + } elseif ($cd1 === 9) { + $cd1 = 0; + $cd2 = Validation::mod11($key . $cd1, 7); + } + } elseif ($cd2 > 1) { + $cd2 = 11 - $cd2; + } + + return $key . $cd1 . $cd2; + } + + /** + * Modifies $dictionary before its UTF8 decoding + */ + protected function updateDictionary() + { + // Change some terms + $this->dictionary = array_replace( + $this->dictionary, + [ + 'agency_code' => 'Agência/Cod. Beneficiário', + 'date_process' => 'Data do processameto', + 'doc_number_sh' => 'Nº do documento', + 'discount' => '(-) Desconto/ Abatimento', + 'doc_value' => 'Valor', + 'doc_valueU' => 'Valor', + 'doc_value=' => '(=) Valor do documento', + 'fine' => '(+) Mora/Multa', + 'guarantor' => 'Sacador/Avalista: ', + 'instructions' => 'Instruções', + 'kind' => 'Espécie doc', + 'currency' => 'Moeda', + ] + ); + + // Make most terms upper case + $keys = [ + 'accept', 'addition', 'agency_code', 'amount', 'assignor', + 'bank_use', 'charged', 'client', 'currency', 'date_document', + 'date_due', 'date_process', 'deduction', 'demonstrative', + 'discount', 'doc_number_sh', 'doc_value', 'doc_value=', + 'doc_valueU', 'fine', 'guarantor', 'instructions', 'kind', + 'mech_auth', 'our_number', 'payment_place', 'wallet', + ]; + foreach ($keys as $key) { + $this->dictionary[$key] = mb_strtoupper($this->dictionary[$key]); + } + } +} diff --git a/src/BankBillet/Views/CaixaEconomicaFederal.php b/src/BankBillet/Views/CaixaEconomicaFederal.php new file mode 100644 index 0000000..f33fc0b --- /dev/null +++ b/src/BankBillet/Views/CaixaEconomicaFederal.php @@ -0,0 +1,133 @@ +dictionary; + + $this->AddPage(); + + $this->drawPageHeader(); + + $this->billetSetFont('cell_data'); + $this->drawDash($dict['client_receipt']); + + $this->drawBillhead(); + + $this->drawTable1(); + + $this->billetSetFont('cell_title'); + $this->drawDash($dict['cut_here'], true); + + $this->drawTable2(); + + $this->drawBarCode(); + + $this->billetSetFont('cell_title'); + $this->drawDash($dict['cut_here'], true); + } + + /** + * Table 1, stays with the Client + * + * Extends drawGenericTable1() + */ + protected function drawTable1() + { + $this->drawBankHeader(); + + $this->drawGenericTable1( + 'LB', + [ + 80.8, 35.4, 11 , 16 , 33.8, + 52.8, 37 , 37.4, 49.8, + 32 , 32 , 32 , 31.2, 49.8, + 177 + ] + ); + + $dict = $this->dictionary; + $fields = $this->fields; + + // Demonstrative + $this->billetSetFont('cell_title'); + $this->Cell(151, 3.5, $fields['demonstrative']['text'], 0, 0); + $this->Cell(26, 3.5, $dict['mech_auth'], 0, 1); + $this->billetSetFont('cell_data'); + $y = $this->GetY(); + $this->MultiCell(151, 3.5, $fields['demonstrative']['value']); + $y1 = $this->GetY(); + $this->SetXY(161, $y); + $this->Cell(26, 3.5, '', 0, 1); + $y2 = $this->GetY(); + $this->SetY(max($y + 14, $y1, $y2)); + $this->Ln(12); + } + + /** + * Table 2, stays in payment place + * + * Extends drawGenericTable2() + */ + protected function drawTable2() + { + $this->drawBankHeader(); + + $this->drawGenericTable2( + 'instructions', + 'LB', + [ + 127.2, 49.8, + 127.2, 49.8, + 32 , 42.2, 18, 11 , 24, 49.8, + 32 , 24 , 16, 34.2, 21, 49.8, + 127.2, 49.8 + ] + ); + + $dict = $this->dictionary; + $fields = $this->fields; + + // Client + $client_data = $fields['client']['value'] . "\n" + . utf8_decode($this->title->client->address->outputLong()); + $this->billetSetFont('cell_title'); + $this->Cell(127.2, 7, $fields['client']['text'], 'L', 1); + $this->billetSetFont('cell_data'); + $this->MultiCell(127.2, 3.5, $client_data, 'LB'); + $this->SetXY(137.2, $this->GetY() - 3.5); + $this->billetSetFont('cell_title'); + $this->Cell(49.8, 3.5, $dict['cod_down'], 'LB', 1); + + // Guarantor + $this->Cell(17, 3.5, $fields['guarantor']['text']); + $this->billetSetFont('cell_data'); + $this->Cell(93, 3.5, $fields['guarantor']['value']); + + // Mechanical authentication + $this->billetSetFont('cell_title'); + $this->Cell(39.5, 3.5, $dict['mech_auth'] . ' - ', 0, 0, 'R'); + $this->billetSetFont('cell_data'); + $this->Cell(27.5, 3.5, $dict['compensation'], 0, 1, 'R'); + } +} diff --git a/src/Controllers/BankBillet.php b/src/Controllers/BankBillet.php deleted file mode 100644 index 1abc52e..0000000 --- a/src/Controllers/BankBillet.php +++ /dev/null @@ -1,61 +0,0 @@ -assignor->bank; - - $view_class = '\\aryelgois\\BankInterchange\\Views\\BankBillets\\' - . $bank->view; - - $this->view = new $view_class($title, $data, $logos); - } - - /** - * Echos the Bank Billet with headers - * - * @param string $name The filename - */ - public function output($name = '') - { - $this->view->Output('I', $name); - } -} diff --git a/src/Controllers/Cnab.php b/src/Controllers/Cnab.php deleted file mode 100644 index f46a2a0..0000000 --- a/src/Controllers/Cnab.php +++ /dev/null @@ -1,94 +0,0 @@ -view = new $view_class($shipping_file); - - $this->filename = $this->view->filename($cnab); - } - - /** - * Outputs the generated Shipping File - * - * @param string $directory Where to save the Shipping File. - * If empty, outputs to stdout. - * - * @return string Name for generated Shipping File - * @return false For failure - */ - public function output($directory = '') - { - return $this->view->output(); - } - - /** - * ... - * - * @return string - */ - public function filename() - { - return $this->filename; - } -} diff --git a/src/Controllers/ReturnFile.php b/src/Controllers/ReturnFile.php deleted file mode 100644 index 37c77ad..0000000 --- a/src/Controllers/ReturnFile.php +++ /dev/null @@ -1,125 +0,0 @@ - array_values($title_list)]); - $assignor = array_unique(array_column($check, 'assignor')); - if (count($assignor) != 1) { - throw new \InvalidArgumentException('Title list is invalid'); - } - $assignor = $assignor[0]; - - $shipping_file = new BankI\Models\ShippingFile; - $shipping_file->assignor = $assignor; - $shipping_file->status = 0; - $shipping_file->save(); - $id = $shipping_file->id; - - foreach ($title_list as $title_id) { - $sft = new BankI\Models\ShippingFileTitle; - $sft->shipping_file = $id; - $sft->title = $title_id; - $sft->save(); - } - - return $id; - } -} diff --git a/src/FilePack/Controller.php b/src/FilePack/Controller.php new file mode 100644 index 0000000..cf54c2f --- /dev/null +++ b/src/FilePack/Controller.php @@ -0,0 +1,203 @@ +views; + } + + /** + * Creates a new instance with models in a Resource, then outputs it + * + * @param Resource $resource Processed MedoolsRouter Resource + * + * @return boolean For success or failure + * + * @throws \LogicException If $resource->model_class is invalid + */ + public static function fromResource(Resource $resource) + { + if (!is_subclass_of($resource->model_class, Medools\Model::class)) { + $message = 'Invalid Resource model_class in ' . static::class . ': ' + . $resource->model_class; + throw new \LogicException($message); + } + + $controller = new static; + + $list = $resource->getList(); + if (empty($list)) { + return false; + } + + foreach ($list as $id) { + if (!$controller->generate($id)) { + return false; + } + } + + if ($resource->content_type === 'application/zip') { + $controller->zip(); + } else { + $controller->output(); + } + + return true; + } + + /** + * Generates the file View from data in a Medools Model + * + * @param mixed $where \Medoo\Medoo $where clause for MODEL_CLASS + * @param string $name Name for the generated file + * + * @return boolean For success or failure + */ + public function generate($where, string $name = null) + { + $model = (static::MODEL_CLASS)::getInstance($where); + if ($model === null) { + return false; + } + + $view = static::getView($model); + + $this->views[] = [ + 'file' => $view, + 'name' => BankInterchange\Utils::addExtension( + $name ?? $view->filename(), + static::EXTENSION + ), + ]; + + return true; + } + + /** + * Generates the View object from a Model + * + * @param Medools\Model $model A MODEL_CLASS instance + * + * @return ViewInterface + */ + abstract protected function getView(Medools\Model $model); + + /** + * Outputs the View with appropriated headers + * + * If there are more than one view objects, it calls zip() instead + * + * @throws \LogicException If there is no view to output + */ + public function output() + { + $count = count($this->views); + + if ($count === 0) { + throw new \LogicException('You need to generate() first'); + } elseif ($count > 1) { + return $this->zip(); + } + + $view = $this->views[0]; + $view['file']->outputFile($view['name']); + } + + /** + * Outputs a zip file containing all views + * + * @param string $name Filename + * + * @throws \LogicException If there is no view to pack + */ + public function zip(string $name = null) + { + if (count($this->views) === 0) { + throw new \LogicException('You need to generate() first'); + } + + Utils::checkOutput('ZIP'); + + $file = tmpfile(); + $filepath = stream_get_meta_data($file)['uri']; + + $zip = new \ZipArchive(); + $zip->open($filepath, \ZipArchive::OVERWRITE); + foreach ($this->views as $view) { + $zip->addFromString($view['name'], $view['file']->getContents()); + } + $zip->close(); + + $name = BankInterchange\Utils::addExtension( + $name ?? 'download', + '.zip' + ); + + header('Content-Type: application/zip'); + header('Content-Length: ' . filesize($filepath)); + header('Content-Disposition: attachment; filename="' . $name . '"'); + readfile($filepath); + unlink($filepath); + } +} diff --git a/src/FilePack/ViewInterface.php b/src/FilePack/ViewInterface.php new file mode 100644 index 0000000..13b65bd --- /dev/null +++ b/src/FilePack/ViewInterface.php @@ -0,0 +1,39 @@ + [ + Assignor::class, + 'person' + ], + 'address' => [ + FullAddress::class, + 'id' + ], + 'bank' => [ + Bank::class, + 'id' + ], + 'document_kind' => [ + DocumentKind::class, + 'id' + ], + 'wallet' => [ + Wallet::class, + 'id' + ], + ]; + + /** + * Formats Agency/Assignor's code + * + * @param integer $agency_length + * @param integer $account_length + * @param boolean $mask If should include mask + * + * @return string + * + * @throws \LengthException @see Utils::padNumber() + */ + public function formatAgencyAccount( + int $agency_length, + int $account_length, + bool $mask = true + ) { + return Utils::padNumber($this->agency, $agency_length) + . ($mask ? '/' : '') + . Utils::padNumber($this->account, $account_length) + . ($mask ? '-' : '') + . $this->account_cd; + } +} diff --git a/src/Models/Assignor.php b/src/Models/Assignor.php index 1fcb4cb..22abfb6 100644 --- a/src/Models/Assignor.php +++ b/src/Models/Assignor.php @@ -1,6 +1,6 @@ [ - '\aryelgois\Medools\Models\Person', - 'id' - ], - 'address' => [ - __NAMESPACE__ . '\FullAddress', - 'id' - ], - 'bank' => [ - __NAMESPACE__ . '\Bank', - 'id' - ], - 'wallet' => [ - __NAMESPACE__ . '\Wallet', + Person::class, 'id' ], ]; - - /** - * Formats Agency/Assignor's code - * - * @param integer $agency_length - * @param integer $account_length - * @param boolean $symbols If should include symbols - * - * @return string - * - * @throws \LengthException @see Utils::padNumber() - */ - public function formatAgencyAccount( - $agency_length, - $account_length, - $symbols = true - ) { - $tmp = [ - BankI\Utils::padNumber($this->agency, $agency_length), - BankI\Utils::padNumber($this->account, $account_length) - ]; - $check_digit = $this->account_cd; - - if ($symbols) { - return implode(' / ', $tmp) . '-' . $check_digit; - } - return implode('', $tmp) . $check_digit; - } } diff --git a/src/Models/Bank.php b/src/Models/Bank.php index 7fbebfa..9e28ea7 100644 --- a/src/Models/Bank.php +++ b/src/Models/Bank.php @@ -1,6 +1,6 @@ [ + Assignor::class, + 'person' + ], 'person' => [ - '\aryelgois\Medools\Models\Person', + Person::class, 'id' ], 'address' => [ - __NAMESPACE__ . '\FullAddress', + FullAddress::class, 'id' ], ]; diff --git a/src/Models/Specie.php b/src/Models/Currency.php similarity index 66% rename from src/Models/Specie.php rename to src/Models/Currency.php index e14be75..fabcf73 100644 --- a/src/Models/Specie.php +++ b/src/Models/Currency.php @@ -1,6 +1,6 @@ decimals, - $this->decimal, - $this->thousand + $format === 'nomask' ? '' : $this->decimal, + $format === 'nomask' ? '' : $this->thousand ); switch ($format) { @@ -63,6 +64,7 @@ public function format($value, $format = 'symbol') break; case 'raw': + case 'nomask': return $formatted; break; diff --git a/src/Models/CurrencyCode.php b/src/Models/CurrencyCode.php new file mode 100644 index 0000000..544b0fb --- /dev/null +++ b/src/Models/CurrencyCode.php @@ -0,0 +1,46 @@ + [ + Currency::class, + 'id' + ], + 'bank' => [ + Bank::class, + 'id' + ], + ]; +} diff --git a/src/Models/DocumentKind.php b/src/Models/DocumentKind.php new file mode 100644 index 0000000..5a11d8c --- /dev/null +++ b/src/Models/DocumentKind.php @@ -0,0 +1,38 @@ + [ + Bank::class, + 'id' + ], + ]; +} diff --git a/src/Models/FullAddress.php b/src/Models/FullAddress.php index 51a94c3..7865acf 100644 --- a/src/Models/FullAddress.php +++ b/src/Models/FullAddress.php @@ -1,6 +1,6 @@ place . ', ' - . $this->number . ', ' - . ($this->detail != '' ? ', ' . $this->detail : '') - . $this->neighborhood . "\n" - . $this->county->name . '/' - . $this->county->state->code . ' - ' - . 'CEP: ' . Validation::cep($this->zipcode); - - return $result; + return implode("\n", array_filter([ + implode(', ', array_filter([ + $this->place, + $this->number, + $this->detail, + ($this->detail == '' ? $this->neighborhood : ''), + ])), + implode(', ', array_filter([ + ($this->detail != '' ? $this->neighborhood : ''), + implode(' - CEP: ', array_filter([ + $this->county->name . '/' . $this->county->state->code, + Validation::cep($this->zipcode), + ])), + ])), + ])); } /** @@ -44,13 +49,15 @@ public function outputLong() */ public function outputShort() { - $result = $this->place . ', ' - . $this->number . ', ' - . $this->neighborhood . ', ' - . $this->county->name . '/' - . $this->county->state->code . ' ' - . Validation::cep($this->zipcode); - - return $result; + return implode(', ', array_filter([ + $this->place, + $this->number, + $this->detail, + $this->neighborhood, + implode(' ', array_filter([ + $this->county->name . '/' . $this->county->state->code, + Validation::cep($this->zipcode), + ])) + ])); } } diff --git a/src/Models/Person.php b/src/Models/Person.php new file mode 100644 index 0000000..756324b --- /dev/null +++ b/src/Models/Person.php @@ -0,0 +1,21 @@ +return_file = $return_file; - $this->cnab = $cnab; - $this->config = $config[$cnab]; - $this->messages = [ - 'error' => [], - 'info' => [], - 'warning' => [], - ]; - $this->registries = [ - 'meta' => [], - 'lots' => [], - ]; - - /* - * Validate Return File - */ - $this->validate(); - - /* - * analyze Return File - */ - $this->analyze(); - } - - /** - * Returns stored messages - */ - public function getMessages() - { - return $this->messages; - } - - /** - * Applies changes to Titles in the Database - * - * @return false If there are no changes - * @return true If run successfully - * @return int[] List of Titles' id which failed to update() - */ - public function apply() - { - if (empty($this->changes)) { - return false; - } - - $failed = []; - - foreach($this->changes as $title_id => $data) { - $title = new BankI\Models\Title($title_id); - $title->setMultiple($data); - if (!$title->update(array_keys($data))) { - $failed[] = $title_id; - } - } - - if (empty($failed)) { - return true; - } - return $failed; - } - - /** - * Validates the Return File registries - */ - protected function validate() - { - $this->matcher_enabled = ['file_header']; - $matcher_enabled_old = null; - foreach ($this->return_file as $line => $registry) { - $matched = false; - if ($matcher_enabled_old != $this->matcher_enabled) { - $matcher = Utils\Utils::arrayWhitelist( - $this->config['matcher'], - $this->matcher_enabled - ); - $matcher_enabled_old = $this->matcher_enabled; - } - foreach ($matcher as $matcher_name => $matcher_data) { - if (preg_match($matcher_data['pattern'], $registry, $matches)) { - $match = array_combine( - $matcher_data['map'], - array_map('trim', array_slice($matches, 1)) - ); - $matched = $matcher_name; - break; - } - } - - if ($matched) { - $this->process($line, $matched, $match); - } else { - $this->messages['error'][] = 'Registry mismatch on line ' . ($line + 1); - } - } - } - - /** - * Specific operations - */ - protected function process($line, $matched, $match) - { - switch ($this->cnab) { - case 240: - switch ($matched) { - case 'file_header': - $meta = Utils\Utils::arrayWhitelist( - $match, - [ - 'record_date', - 'record_time', - 'file_sequence', - 'assignor_use', - ] - ); - - $assignor = $this->findAssignor($line, $match); - if ($assignor) { - $meta['assignor'] = $assignor; - } - - $this->registries['meta'] = $meta; - $this->matcher_enabled = ['lot_header', 'file_trailer']; - break; - - case 'lot_header': - $lot = [ - 'meta' => Utils\Utils::arrayWhitelist( - $match, - [ - 'message1', - 'message2', - 'shipping_return_number', - 'shipping_return_record_date', - 'credit_date', - ] - ), - 'data' => [], - 'registries' => 1, - ]; - - $assignor = $this->findAssignor($line, $match); - if ($assignor) { - $lot['meta']['assignor'] = $assignor; - } - - $this->registries['lots'][(int) $match['lot']] = $lot; - $this->matcher_enabled = [ - 'title_t', - 'title_u', - 'lot_trailer' - ]; - break; - - case 'title_t': - $data = Utils\Utils::arrayWhitelist( - $match, - [ - 'movement', - 'doc_number', - 'our_number', - 'due', - 'value', - 'receiver_bank', - 'receiver_agency', - 'receiver_agency_cd', - 'assignor_use', - 'specie', - 'contract', - 'tax', - 'occurrence', - ] - ); - - $title = $this->findTitle($line, $match); - if ($title) { - $data['title'] = $title; - } - - $this->registries['lots'][(int) $match['lot']]['data'][(int) $match['lot_registry']] = array_merge( - $this->registries['lots'][(int) $match['lot']]['data'][(int) $match['lot_registry']] ?? [], - $data - ); - $this->registries['lots'][(int) $match['lot']]['registries']++; - break; - - case 'title_u': - $data = Utils\Utils::arrayWhitelist( - $match, - [ - 'value_paid', - 'value_net', - 'expenses', - 'credits', - 'occurrence_date', - 'credit_date', - 'payer_occurrence_code', - 'payer_occurrence_date', - 'payer_occurrence_value', - 'payer_occurrence_detail', - 'corresponding_bank', - 'corresponding_bank_our_number', - ] - ); - - $this->registries['lots'][(int) $match['lot']]['data'][(int) $match['lot_registry']] = array_merge( - $this->registries['lots'][(int) $match['lot']]['data'][(int) $match['lot_registry']] ?? [], - $data - ); - $this->registries['lots'][(int) $match['lot']]['registries']++; - break; - - case 'lot_trailer': - $data = Utils\Utils::arrayWhitelist( - $match, - [ - 'title_cs_count', - 'title_cs_total', - 'title_cv_count', - 'title_cv_total', - 'title_cc_count', - 'title_cc_total', - 'title_cd_count', - 'title_cd_total', - 'warning', - ] - ); - - if (++$this->registries['lots'][(int) $match['lot']]['registries'] != (int) $match['lot_registry_count']) { - $this->messages['error'][] = "Lot {$match['lot']} has different ammount of registries from it's Trailer"; - } - - $this->registries['lots'][(int) $match['lot']]['meta'] = array_merge( - $this->registries['lots'][(int) $match['lot']]['meta'] ?? [], - $data - ); - $this->matcher_enabled = [ - 'lot_header', - 'file_trailer' - ]; - break; - - case 'file_trailer': - if (count($this->registries['lots']) != $match['lot_count']) { - $this->messages['error'][] = "Lot count differ"; - } - - $registry_count = array_sum( - array_column( - $this->registries['lots'], - 'registries' - ) - ); - if ($registry_count + 2 != $match['registry_count']) { - $this->messages['error'][] = "Registry count differ"; - } - - $this->matcher_enabled = []; - break; - } - break; - - case 400: - switch ($matched) { - case 'file_header': - $meta = Utils\Utils::arrayWhitelist( - $match, - [ - 'record_date', - 'agency_account', - 'assignor_document', - 'file_sequence', - ] - ); - $lot = [ - 'meta' => [], - 'data' => [], - ]; - - // how the assignor account is stored differ from banks - - $this->registries['meta'] = $meta; - $this->registries['lots'][0] = $lot; - $this->matcher_enabled = ['title', 'file_trailer']; - break; - - case 'title': - $data = Utils\Utils::arrayWhitelist( - $match, - [ - 'assignor_use', - 'our_number', - 'occurrence', - 'occurrence_date', - 'your_number', - 'due', - 'value', - 'receiver_bank', - 'receiver_agency', - 'tax', - 'expenses', - 'value_paid', - 'late_fine', - 'credits', - 'late_confirm', - 'late_confirm_charge', - 'discount_confirm', - 'discount_value_confirm', - 'instruction1_confirm', - 'instruction2_confirm', - 'protest_confirm', - 'specie', - ] - ); - - $title = $this->findTitle($line, $match); - if ($title) { - $data['title'] = $title; - } - - $this->registries['lots'][0]['data'][(int) $match['registry']] = $data; - $this->matcher_enabled = ['title', 'file_trailer']; - break; - - case 'file_trailer': - $meta = Utils\Utils::arrayWhitelist( - $match, - [ - 'title_cs_count', - 'title_cs_total', - 'warning_cs', - 'title_cv_count', - 'title_cv_total', - 'warning_cv', - 'title_cc_count', - 'title_cc_total', - 'warning_cc', - 'title_cd_count', - 'title_cd_total', - 'warning_cd', - ] - ); - - $count = $match['title_cs_count'] - + $match['title_cv_count'] - + $match['title_cc_count'] - + $match['title_cd_count']; - - if (count($this->registries['lots'][0]['data']) != $count) { - $this->messages['error'][] = "Title count differ"; - } - - $this->registries['lots'][0]['meta'] = $meta; - $this->matcher_enabled = []; - break; - } - break; - } - } - - /** - * Analyzes each registry in the Return File - * - * It looks for messages from the Bank and for changes to apply in the - * Database. - */ - protected function analyze() - { - if ($this->cnab == 240) { - $movements = $this->config['fields']['movement']; - $movements_flatten = array_replace(...array_values($movements)); - } - $occurrences = $this->config['fields']['occurrence']; - - foreach ($this->registries['lots'] as $lot_id => $lot) { - foreach ($lot['data'] as $registry_id => $registry) { - $message = [ - 'our_number' => $registry['our_number'], - 'receiver_bank' => $registry['receiver_bank'], - 'receiver_agency' => $registry['receiver_agency'] . (isset($registry['receiver_agency_cd']) ? '-' . $registry['receiver_agency_cd'] : ''), - ]; - - /* - * Format Monetary values - */ - $data = [ - 'value' => $registry['value'], - 'value_paid' => $registry['value_paid'] ?? null, - ]; - foreach ($data as $i => $v) { - if (isset($registry['title'])) { - if ($v !== null) { - $v = $registry['title']->specie->format($v); - } - } else { - $v = ltrim($v, '0'); - if ($v === '') { - $v = '0'; - } - } - $message[$i] = $v; - } - - /* - * Prepare Dates - */ - $date_format = ''; - $dates = []; - if ($registry['due'] > 0) { - $dates['due'] = $registry['due']; - } else { - $message['due'] = '0000-00-00'; - } - - /* - * Main block - */ - switch ($this->cnab) { - case 240: - $date_format = 'dmY'; - - $movement = $registry['movement']; - $occurrence = $registry['occurrence']; - - $message['movement'] = $movements_flatten[$movement]; - $occurrence_group = null; - foreach ($this->config['fields']['relation'] as $group => $list) { - if (in_array($movement, $list)) { - $occurrence_group = $group; - break; - } - } - - if ($occurrence_group) { - $message['occurrence'] = $occurrences[$occurrence_group][$occurrence]; - } else { - $this->messages['warning'][] = 'Unknown occurrence in registry ' . $registry_id . ' (lot ' . $lot_id . ')'; - } - - if (isset($registry['occurrence_date'])) { - $dates['occurrence_date'] = $registry['occurrence_date']; - } - - if (isset($registry['title'])) { - if (array_key_exists($movement, $movements['error'])) { - $this->changes[$registry['title']->id] = [ - 'status' => 1 - ]; - } elseif (array_key_exists($movement, $movements['info'])) { - $data = ['status' => 0]; - if (isset($registry['value_paid'])) { - $data['value_paid'] = ($registry['value_paid'] / 10 ** $registry['title']->specie->decimals); - } - $this->changes[$registry['title']->id] = $data; - } else { - $this->messages['warning'][] = 'Could not identify movement in registry ' . $registry_id . ' (lot ' . $lot_id . ')'; - } - } - break; - - case 400: - $date_format = 'dmy'; - - $occurrence = $registry['occurrence']; - - $message['occurrence'] = $this->config['fields']['occurrence'][$occurrence]; - - if (isset($registry['occurrence_date'])) { - $dates['occurrence_date'] = $registry['occurrence_date']; - } - - if (isset($registry['title'])) { - if ($occurrence == "51") { - $this->changes[$registry['title']->id] = [ - 'status' => 1 - ]; - } else { - $data = ['status' => 0]; - $value_paid = $registry['value_paid'] / 10 ** $registry['title']->specie->decimals; - if ($value_paid > 0) { - $data['value_paid'] = $value_paid; - } - $this->changes[$registry['title']->id] = $data; - } - } - break; - } - - /* - * Format Dates - */ - foreach ($dates as $k => $v) { - if ($date_format) { - $date = \DateTime::createFromFormat($date_format, $v); - if ($date) { - $v = $date->format('Y-m-d'); - } - } - $message[$k] = $v; - } - - $this->messages['info'][] = $message; - } - } - } - - /* - * Helper - * ========================================================================= - */ - - protected function findAssignor($line, $match) - { - $assignor = new BankI\Models\Assignor; - $database = $assignor->getDatabase(); - - // Find based on document and covenant - $result = $database->select( - 'assignors', - [ - '[><]people' => ['person' => 'id'], - ], - ['assignors.id'], - [ - 'people.document[~]' => (int) $match['assignor_document'], - 'assignors.covenant[~]' => (int) $match['assignor_covenant'], - ] - ); - - if (empty($result)) { - $this->messages['warning'][] = 'Assignor in line ' . ($line + 1) . ' not found in the Database'; - } else { - $id = $result[0]['id']; - if (count($result) > 1) { - $this->messages['warning'][] = 'Multiple Assignors found in line ' . ($line + 1) . ". Using id $id"; - } - $assignor->load($id); - return $assignor; - } - } - - protected function findTitle($line, $match) - { - $title = new BankI\Models\Title; - - $loaded = $title->load( - ['our_number[~]' => (int) $match['our_number']] - ); - - if (!$loaded) { - $this->messages['warning'][] = 'Our number in line ' . ($line + 1) . ' not found in the Database'; - } else { - return $title; - } - } -} diff --git a/src/Models/ShippingFile.php b/src/Models/ShippingFile.php index eb5c73d..bebe085 100644 --- a/src/Models/ShippingFile.php +++ b/src/Models/ShippingFile.php @@ -1,6 +1,6 @@ 'auto', + 'stamp' => 'auto', ]; const OPTIONAL_COLUMNS = [ - 'status', - 'stamp', - 'update', + 'notes', ]; const FOREIGN_KEYS = [ - 'assignor' => [ - __NAMESPACE__ . '\Assignor', + 'assignment' => [ + Assignment::class, 'id' ], ]; + + /** + * Returns a Iterator of Title models related to this object + * + * @return Medools\ModelIterator + */ + public function getTitles() + { + return Title::getIterator(['shipping_file' => $this->id]); + } } diff --git a/src/Models/ShippingFileMovements.php b/src/Models/ShippingFileMovements.php new file mode 100644 index 0000000..818f9da --- /dev/null +++ b/src/Models/ShippingFileMovements.php @@ -0,0 +1,37 @@ + [ + Bank::class, + 'id' + ], + ]; +} diff --git a/src/Models/ShippingFileTitle.php b/src/Models/ShippingFileTitle.php deleted file mode 100644 index 9210a3a..0000000 --- a/src/Models/ShippingFileTitle.php +++ /dev/null @@ -1,45 +0,0 @@ - [ - __NAMESPACE__ . '\ShippingFile', - 'id' - ], - 'title' => [ - __NAMESPACE__ . '\Title', - 'id' - ], - ]; -} diff --git a/src/Models/Title.php b/src/Models/Title.php index 791c4a5..088c251 100644 --- a/src/Models/Title.php +++ b/src/Models/Title.php @@ -1,6 +1,6 @@ 'auto', + 'stamp' => 'auto', ]; const OPTIONAL_COLUMNS = [ + 'shipping_file', + 'movement', 'guarantor', - 'status', - 'doc_type', + 'accept', 'value_paid', 'fine_type', 'fine_date', 'fine_value', - 'discount_type', - 'discount_date', - 'discount_value', - 'stamp', - 'update', + 'interest_type', + 'interest_date', + 'interest_value', + 'discount1_type', + 'discount1_date', + 'discount1_value', + 'discount2_type', + 'discount2_date', + 'discount2_value', + 'discount3_type', + 'discount3_date', + 'discount3_value', + 'protest_code', + 'protest_days', + 'description', + 'occurrence', + 'occurrence_date', ]; const FOREIGN_KEYS = [ - 'assignor' => [ - __NAMESPACE__ . '\Assignor', + 'shipping_file' => [ + ShippingFile::class, + 'id' + ], + 'movement' => [ + ShippingFileMovements::class, 'id' ], - 'payer' => [ - __NAMESPACE__ . '\Payer', + 'assignment' => [ + Assignment::class, + 'id' + ], + 'client' => [ + Client::class, 'id' ], 'guarantor' => [ - __NAMESPACE__ . '\Payer', + Client::class, + 'id' + ], + 'currency' => [ + Currency::class, 'id' ], - 'specie' => [ - __NAMESPACE__ . '\Specie', + 'kind' => [ + DocumentKind::class, 'id' ], ]; @@ -89,57 +138,60 @@ class Title extends Medools\Model /** * Calculates this model's `our_number` check digit * + * @param integer $base @see aryelgois\Utils\Validation::mod11() $base + * * @return string */ - public function checkDigitOurNumber() + public function checkDigitOurNumber($base = 9) { - return self::checkDigitOurNumberAlgorithm($this->our_number); + return self::checkDigitOurNumberAlgorithm($this->our_number, $base); } /** * Calculates Our number check digit * - * @param string $our_number Value to calculate the check digit + * @param string $our_number Value to calculate the check digit + * @param integer $base @see aryelgois\Utils\Validation::mod11() $base * * @return string */ - public static function checkDigitOurNumberAlgorithm($our_number) + public static function checkDigitOurNumberAlgorithm($our_number, $base = 9) { - $digit = Utils\Validation::mod11($our_number); + $digit = Utils\Validation::mod11($our_number, $base); $digit = ($digit > 1) - ? $digit - 11 - : 0; + ? $digit - 11 + : 0; return abs($digit); } /** - * Sets the `our_number` column based on current `assignor` - * - * Intended to be used only when creating a new entry + * Returns the Title value, considering its tax * - * NOTE: - * - Be sure to save() soon + * @return float + */ + public function getActualValue() + { + $val = $this->value + ($this->tax_included ? 0 : $this->tax_value); + return (float) $val; + } + + /** + * Returns the correct CurrencyCode * - * @throws \LogicException If assignor is not set + * @return CurrencyCode + * @return null If foreigns aren't set */ - public function setOurNumber() + public function getCurrencyCode() { - $assignor = $this->assignor; - if ($assignor === null) { - throw new LogicException('You MUST set `assignor` column first'); + if (!isset($this->currency, $this->assignment->bank)) { + return null; } - $database = self::getDatabase(); - $our_number = $database->max( - static::TABLE, - 'our_number', - [ - 'assignor' => $assignor - ] - ); - - $this->our_number = ++$our_number; + return CurrencyCode::getInstance([ + 'currency' => $this->currency->id, + 'bank' => $this->assignment->bank->id + ]); } } diff --git a/src/Models/Wallet.php b/src/Models/Wallet.php index bbcc067..8be78f7 100644 --- a/src/Models/Wallet.php +++ b/src/Models/Wallet.php @@ -1,6 +1,6 @@ [ + Bank::class, + 'id' + ], ]; } diff --git a/src/ReturnFile/Extractor.php b/src/ReturnFile/Extractor.php new file mode 100644 index 0000000..45d9050 --- /dev/null +++ b/src/ReturnFile/Extractor.php @@ -0,0 +1,235 @@ +output(); + + $this->bank = Models\Bank::getInstance([ + 'code' => $parsed['bank_code'], + ]); + + $this->cnab = $parsed['cnab']; + + $this->config = $return_file->getConfig(); + + $this->registries = $parsed['registries']; + + $this->result = [ + 'return_file' => $this->extractReturnFile(), + 'titles' => $this->extractTitles(), + ]; + } + + /** + * Outputs extracted data + * + * @return array[] + */ + public function output() + { + return $this->result; + } + + /* + * Extractors + * ========================================================================= + */ + + /** + * Extracts data about the Return File + * + * @return mixed[] + */ + protected function extractReturnFile() + { + $header = $this->registries[0]; + + return [ + 'sequence' => (int) $header->file_sequence, + 'emission' => static::parseDate($header->record_date), + 'charging' => $this->extractCharging(), + ]; + } + + /** + * Extracts data about the titles charging + * + * @return mixed[] + */ + abstract protected function extractCharging(); + + /** + * Extracts data about each Title + * + * @return array[] + */ + abstract protected function extractTitles(); + + /* + * Helper + * ========================================================================= + */ + + /** + * Detects an Assignment in the database + * + * @param Registry $title Registry with title's data + * + * @return Models\Assignment On success + * @return null On failure + */ + protected function detectAssignment(Registry $title) + { + return Models\Assignment::getInstance([ + 'bank' => $this->bank->id, + 'agency' => $title->assignment_agency, + 'account' => $title->assignment_account, + 'cnab' => (string) $this->cnab, + ]); + } + + /** + * Detects a Title in the database + * + * @param mixed[] $title Extracted title data + * + * @return string On success + * @return null On failure + */ + protected static function detectTitle(array $title) + { + $assignment = $title['assignment']; + if ($assignment === null) { + return; + } + + $where = array_merge( + Utils\Utils::arrayWhitelist($title, [ + 'assignment', + 'our_number', + 'due', + ]), + [ + 'ORDER' => [ + 'stamp' => 'DESC', + 'id' => 'DESC', + ], + ] + ); + + $ids = Models\Title::dump($where, 'id'); + if (empty($ids)) { + return; + } + return $ids[0]; + } + + /** + * Computes a human readable occurrence from a title + * + * NOTE: + * - The default behavior is to do nothing. It is not abstract because some + * children classes may not use it. + * + * @param Registry $title Registry with title's data + * + * @return string When defined by a child class + * @return null When disabled. You should fallback to the occurrence code + */ + protected function occurrence(Registry $title) + { + return; + } + + /** + * Parses a date to Y-m-d + * + * @param string $date Date string + * @param string $format Expected date $format (defaults to DATE_FORMAT) + * @param string $output Iutput format (defaults to Y-m-d) + * + * @return string On success + * @return null On failure + */ + protected static function parseDate( + string $date, + string $format = null, + string $output = null + ) { + $d = \DateTime::createFromFormat($format ?? static::DATE_FORMAT, $date); + return ($d ? $d->format($output ?? 'Y-m-d') : null); + } +} diff --git a/src/ReturnFile/Extractors/Cnab240.php b/src/ReturnFile/Extractors/Cnab240.php new file mode 100644 index 0000000..fb3bd05 --- /dev/null +++ b/src/ReturnFile/Extractors/Cnab240.php @@ -0,0 +1,147 @@ +registries) - 1; + for ($lot_id = 1; $lot_id < $count; $lot_id++) { + $buffer = []; + $registry = $this->registries[$lot_id][2]; + + foreach (static::CHARGING_TYPES as $type) { + $count = "${type}_count"; + $total = "${type}_total"; + + $buffer = array_merge( + $buffer, + [ + $count => (int) $registry->{$count}, + $total => (float) ($registry->{$total} / 100.0), + ] + ); + } + $buffer['warning'] = (int) $registry->warning; + + $lot_result[] = $buffer; + } + + $result = array_shift($lot_result); + foreach ($lot_result as $arr) { + foreach ($arr as $key => $val) { + $result[$key] += $val; + } + }; + + return $result; + } + + /** + * Extracts data about each Title + * + * @return array[] + */ + protected function extractTitles() + { + $result = []; + $count = count($this->registries) - 1; + for ($lot_id = 1; $lot_id < $count; $lot_id++) { + $buffer = []; + + foreach ($this->registries[$lot_id][1] as $registry) { + switch ($registry->getType()) { + case 'TITLE_T': + $data = [ + 'assignment' => $this->detectAssignment($registry)->id ?? null, + 'our_number' => (int) $registry->our_number, + 'value' => (float) ($registry->value / 100.0), + 'tax' => (float) ($registry->tax / 100.0), + 'due' => static::parseDate($registry->due), + 'occurrence' => $this->occurrence($registry) ?? $registry->occurrence, + ]; + $data['id'] = static::detectTitle($data); + break; + + case 'TITLE_U': + $data = [ + 'value_received' => (float) ($registry->value_received / 100.0), + 'occurrence_date' => static::parseDate($registry->occurrence_date), + ]; + break; + } + + $key = $registry->lot_registry; + $buffer[$key] = array_merge($buffer[$key] ?? [], $data); + } + + $result = array_merge($result, array_values($buffer)); + } + + return $result; + } + + /* + * Helper + * ========================================================================= + */ + + /** + * Computes a human readable occurrence from a title + * + * @param ReturnFile\Registry $registry Registry with title's data + * + * @return string + */ + protected function occurrence(ReturnFile\Registry $registry) + { + $config = $this->config; + + $movement = $registry->movement; + $occurrence = $registry->occurrence; + + $result = [ + "Movimento $movement", + "Ocorrência $occurrence", + ]; + $glue = ', '; + + $tmp = $config['movement'][$movement] ?? null; + if ($tmp !== null) { + $result[0] = $tmp; + $glue = ': '; + } + + $group = $config['movement_to_occurrence'][$movement] ?? null; + if (array_key_exists($group, $config['occurrence'])) { + $tmp = $config['occurrence'][$group][$occurrence] ?? null; + if ($tmp !== null) { + $result[1] = $tmp; + } + } + + return implode($glue, $result); + } +} diff --git a/src/ReturnFile/Extractors/Cnab240/Banese.php b/src/ReturnFile/Extractors/Cnab240/Banese.php new file mode 100644 index 0000000..b924e1d --- /dev/null +++ b/src/ReturnFile/Extractors/Cnab240/Banese.php @@ -0,0 +1,31 @@ +registries[2]; + + foreach (static::CHARGING_TYPES as $type) { + $count = "${type}_count"; + $total = "${type}_total"; + $warning = "${type}_warning"; + + $result = array_merge( + $result, + [ + $count => (int) $trailer->{$count}, + $total => (float) ($trailer->{$total} / 100.0), + $warning => (int) $trailer->{$warning}, + ] + ); + } + + return $result; + } + + /** + * Extracts data about each Title + * + * @return array[] + */ + protected function extractTitles() + { + $result = []; + + foreach ($this->registries[1] as $id => $title) { + $data = [ + 'assignment' => $this->detectAssignment($title)->id ?? null, + 'our_number' => (int) $title->our_number, + 'value' => (float) ($title->value / 100.0), + 'tax' => (float) ($title->tax / 100.0), + 'value_received' => (float) ($title->value_received / 100.0), + 'due' => static::parseDate($title->due), + 'occurrence' => $this->occurrence($title) ?? $title->occurrence, + 'occurrence_date' => static::parseDate($title->occurrence_date), + ]; + $data['id'] = static::detectTitle($data); + $result[] = $data; + } + + return $result; + } + + /* + * Helper + * ========================================================================= + */ + + /** + * Computes a human readable occurrence from a title + * + * @param ReturnFile\Registry $registry Registry with title's data + * + * @return string|null + */ + protected function occurrence(ReturnFile\Registry $registry) + { + return $this->config['occurrence'][$registry->occurrence] ?? null; + } +} diff --git a/src/ReturnFile/Extractors/Cnab400/BancoDoNordeste.php b/src/ReturnFile/Extractors/Cnab400/BancoDoNordeste.php new file mode 100644 index 0000000..51fc2a2 --- /dev/null +++ b/src/ReturnFile/Extractors/Cnab400/BancoDoNordeste.php @@ -0,0 +1,45 @@ +config['error_table'], + array_filter(str_split($registry->error_table)) + ); + + return (empty($errors)) + ? $occurrence + : implode("\n- ", array_merge([$occurrence ?? 'Erros:'], $errors)); + } +} diff --git a/src/ReturnFile/Extractors/Cnab400/Banese.php b/src/ReturnFile/Extractors/Cnab400/Banese.php new file mode 100644 index 0000000..3819f3a --- /dev/null +++ b/src/ReturnFile/Extractors/Cnab400/Banese.php @@ -0,0 +1,27 @@ +registries = $registries; + + $message = "Invalid registry at line $line: expecting " + . Format::naturalLanguageJoin($registries, 'or') + . " (CNAB$config)"; + + parent::__construct($message, 0, $previous); + } + + /** + * Returns registry types that did not match + * + * @return string[] + */ + public function getRegistries() + { + return $this->registries; + } +} diff --git a/src/ReturnFile/Parser.php b/src/ReturnFile/Parser.php new file mode 100644 index 0000000..12112ca --- /dev/null +++ b/src/ReturnFile/Parser.php @@ -0,0 +1,342 @@ + 0, + 400 => 76, + ]; + + /** + * Holds loaded configs + * + * @var array[] + */ + protected static $cache = []; + + /** + * Path to directory with config files + * + * @var string + */ + protected static $config_path; + + /** + * Bank that generated the Return File + * + * @var string + */ + protected $bank_code; + + /** + * Which CNAB the Return File might be + * + * @var int + */ + protected $cnab; + + /** + * Config from $cache being used + * + * @var string + */ + protected $config; + + /** + * Contains Return File registries + * + * @var string[] + */ + protected $registries; + + /** + * Contains parsed registries + * + * @var mixed[] + */ + protected $result; + + /** + * Creates a new return file Parser Object + * + * @param string $raw Return File to be parsed + * + * @throws \InvalidArgumentException If Return File is empty + * @throws \UnexpectedValueException If there are non-empty lines after the + * Return File + */ + public function __construct(string $raw) + { + $return_file = rtrim(str_replace("\r", '', $raw), "\n"); + if (empty($return_file)) { + throw new \InvalidArgumentException('Return File is empty'); + } + $registries = explode("\n", $return_file); + + $this->detect($registries); + $this->loadConfig(); + + foreach ($registries as &$registry) { + $registry = str_pad($registry, $this->cnab); + } + unset($registry); + $this->registries = $registries; + + $this->result = $this->parse(self::$cache[$this->config]['structure']); + + $last = $this->result['offset']; + if ($last < count($registries)) { + $message = 'Unexpected content at line ' . ($last + 1) + . ': expecting EOF'; + throw new \UnexpectedValueException($message); + } + } + + /** + * Detects the CNAB layout and the Bank Code + * + * @param string[] $registries Return File registries + * + * @throws \DomainException If could not detect Return File layout + */ + protected function detect(array $registries) + { + $length = max(array_map('strlen', $registries)); + + $found = false; + foreach (self::DETECT_LAYOUT as $cnab => $start) { + if ($length <= $cnab) { + $found = true; + break; + } + } + + if (!$found) { + throw new \DomainException('Could not detect Return File layout'); + } + + $this->bank_code = substr($registries[0], $start, 3); + $this->cnab = $cnab; + } + + /** + * Extracts useful data from the parsed registries in a fixed layout + * + * @return Extractor + * + * @throws \LogicException If could not find Bank in the Database + */ + public function extract() + { + $bank = BankInterchange\Models\Bank::getInstance([ + 'code' => $this->bank_code, + ]); + + if ($bank === null) { + $message = "Extractor class for CNAB$this->config not found"; + throw new \LogicException($message); + } + + $extractor_class = __NAMESPACE__ . "\\Extractors\\Cnab$this->cnab\\" + . BankInterchange\Utils::toPascalCase($bank->name); + + return new $extractor_class($this); + } + + /** + * Returns config being used + * + * @return array[] + */ + public function getConfig() + { + return self::$cache[$this->config]; + } + + /** + * Loads YAML config file into cache + * + * @throws \BadMethodCallException If called before setConfigPath() + * @throws \RuntimeException If config file does not exist + * @throws Yaml\Exception\ParseException If could not load config file + */ + protected function loadConfig() + { + if (self::$config_path === null) { + throw new \BadMethodCallException('Config path is null'); + } + + $key = "$this->cnab/$this->bank_code"; + + if (!array_key_exists($key, self::$cache)) { + $config_file = self::$config_path . "/cnab$key.yml"; + if (file_exists($config_file)) { + self::$cache[$key] = Yaml::parseFile($config_file); + } else { + throw new \RuntimeException(sprintf( + "Config file for Bank '%s' in CNAB%s not found", + $this->bank_code, + $this->cnab + )); + } + } + + $this->config = $key; + } + + /** + * Outputs parsed registries + * + * @return mixed[] + */ + public function output() + { + return [ + 'bank_code' => $this->bank_code, + 'cnab' => $this->cnab, + 'registries' => $this->result['registries'], + ]; + } + + /** + * Recursively follows the Return File structure to preg-match its fields + * + * @param array $structure Structure tree to be used + * @param int $offset Current item in $registries + * + * @return mixed[] + * + * @throws ParseException For invalid registry + */ + protected function parse(array $structure, int $offset = null) + { + $result = []; + $current = $offset ?? 0; + + $count_structure = 0; + foreach ($structure as $registry_group) { + if (is_array($registry_group)) { + $buffer = []; + do { + try { + $rec = $this->parse($registry_group, $current); + $nested = (count($registry_group) > 1) + ? [$rec['registries']] + : $rec['registries']; + $buffer = array_merge($buffer, $nested); + $current = $rec['offset']; + } catch (ParseException $e) { + if (count($buffer) === 0) { + throw $e; + } else { + $first = $registry_group[0]; + if (is_string($first)) { + $first = explode(' ', $first); + } + $in_first = array_intersect( + $first, + $e->getRegistries() + ); + if (count($in_first) === 0) { + throw $e; + } + break; + } + } + } while ($current < count($this->registries)); + $nested = (count($registry_group) > 1) + ? $buffer + : [$buffer]; + $result = array_merge($result, $nested); + } else { + $types = explode(' ', $registry_group); + $registry = $this->pregRegistry($current, $types); + + if ($registry !== null) { + $result[] = $registry; + $current++; + } elseif (count($result) === 0 || $count_structure > 0) { + throw new ParseException( + $this->config, + $types, + $current + 1 + ); + } + } + $count_structure++; + } + + return [ + 'offset' => $current, + 'registries' => $result, + ]; + } + + /** + * Preg-match fields from a Return File registry to fill a Registry instance + * + * @param int $id $registries id to be parsed + * @param string[] $types Registry types to test + * + * @return Registry On success + * @return null On failure + */ + protected function pregRegistry(int $id, array $types) + { + $registry_types = Utils\Utils::arrayWhitelist( + self::$cache[$this->config]['registries'], + $types + ); + + $result = null; + foreach ($registry_types as $type => $matcher) { + if (preg_match( + $matcher['pattern'], + $this->registries[$id] ?? '', + $matches + )) { + $match = array_combine( + $matcher['map'], + array_map('trim', array_slice($matches, 1)) + ); + $result = new Registry($this->config, $type, $match); + break; + } + } + return $result; + } + + /** + * Sets path to directory with config files + * + * @param string $path Path to directory with config files + */ + public static function setConfigPath(string $path) + { + self::$config_path = $path; + self::$cache = []; + } +} diff --git a/src/ReturnFile/Registry.php b/src/ReturnFile/Registry.php new file mode 100644 index 0000000..2eaec58 --- /dev/null +++ b/src/ReturnFile/Registry.php @@ -0,0 +1,122 @@ +config = $config; + $this->data = $data; + $this->type = $type; + } + + /** + * Returns a field stored in $data + * + * @param string $field A valid $data key + * + * @return mixed Almost always string, but may have a numeric value + * + * @throws \DomainException If the field is not found + */ + public function __get($field) + { + if (array_key_exists($field, $this->data)) { + return $this->data[$field]; + } + throw new \DomainException(sprintf( + "Invalid field '%s' for a %s registry (CNAB%s)", + $field, + $this->type, + $this->config + )); + } + + /** + * Returns the stored Return File config key + * + * @return string + */ + public function getConfig() + { + return $this->config; + } + + /** + * Returns the stored data + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Returns the stored type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Outputs useful data from the Registry + * + * @return mixed[] + */ + public function jsonSerialize() + { + return [ + 'type' => $this->type, + 'data' => $this->data, + ]; + } +} diff --git a/src/ReturnFile/Router.php b/src/ReturnFile/Router.php new file mode 100644 index 0000000..60eb919 --- /dev/null +++ b/src/ReturnFile/Router.php @@ -0,0 +1,159 @@ + 'application/x.bank-interchange.return-file_extracted+json', + 'parsed' => 'application/x.bank-interchange.return-file_parsed+json', + ]; + + /** + * When requested route is '/' + * + * @param array $headers Request Headers + * @param string $body Request Body + * + * @return Response With parsed or extracted Return File + */ + protected function requestRoot(array $headers, string $body) + { + if ($this->method !== 'POST') { + $this->sendError( + static::ERROR_METHOD_NOT_IMPLEMENTED, + "Method '$this->method' is not implemented", + 'POST' + ); + } + + $content_type = $headers['Content-Type'] ?? ''; + $content_type = static::parseContentType($content_type); + $mime = $content_type['mime'] ?? $content_type; + + if (empty($mime) || empty(trim($body))) { + $this->sendError( + static::ERROR_INVALID_PAYLOAD, + 'Expecting payload' + ); + } + + if (!in_array($mime, static::SUPPORTED_CONTENT_TYPES)) { + $this->sendError( + static::ERROR_UNSUPPORTED_MEDIA_TYPE, + "Media-Type '$mime' is not supported" + ); + } + + if (($charset = $content_type['charset'] ?? null) !== null) { + $body = mb_convert_encoding($body, 'UTF-8', $charset); + } + + $available = static::AVAILABLE_CONTENT_TYPES; + $accepted = static::getAcceptedType( + $headers['Accept'] ?? '*/*', + $available + ); + if ($accepted === false) { + $message = 'Can not generate content complying to Accept header'; + $this->sendError(static::ERROR_NOT_ACCEPTABLE, $message); + } + + try { + $return_file = new Parser($body); + } catch (\InvalidArgumentException $e) { + $code = static::ERROR_INVALID_PAYLOAD; + } catch (\DomainException $e) { + $code = static::ERROR_INVALID_PAYLOAD; + } catch (ParseException $e) { + $code = static::ERROR_INVALID_PAYLOAD; + } catch (\UnexpectedValueException $e) { + $code = static::ERROR_INVALID_PAYLOAD; + } catch (\Exception $e) { + $code = static::ERROR_INTERNAL_SERVER; + } finally { + if (isset($code)) { + $this->sendError($code, $e->getMessage()); + } + } + + if ($accepted === $available['extracted']) { + try { + $extractor = $return_file->extract(); + } catch (\Exception $e) { + $this->sendError( + static::ERROR_INTERNAL_SERVER, + $e->getMessage() + ); + } + + $data = $extractor->output(); + + $authorization = $this->getAuthorizedResources('GET'); + $assignments = (array_key_exists('assignments', $authorization)) + ? Models\Assignment::dump($authorization['assignments'], 'id') + : []; + $titles = (array_key_exists('titles', $authorization)) + ? Models\Title::dump($authorization['titles'], 'id') + : []; + + foreach ($data['titles'] as &$title) { + if (!in_array($title['assignment'], $assignments)) { + $title['assignment'] = null; + } + if (!in_array($title['id'], $titles)) { + $title['id'] = null; + } + } + unset($title); + } else { + $data = $return_file->output(); + } + + $response = $this->prepareResponse(); + $response->headers['Content-Type'] = $accepted; + $response->body = $data; + + return $response; + } +} diff --git a/src/ShippingFile/Controller.php b/src/ShippingFile/Controller.php new file mode 100644 index 0000000..0ac7bea --- /dev/null +++ b/src/ShippingFile/Controller.php @@ -0,0 +1,121 @@ +query['with_billets'])) { + return static::withBillets($resource); + } + return parent::fromResource($resource); + } + + /** + * Returns a ShippingFile View + * + * @param Medools\Model $model A ShippingFile Model + * + * @return BankInterchange\FilePack\ViewInterface + */ + protected function getView(Medools\Model $model) + { + $assignment = $model->assignment; + + $view_class = __NAMESPACE__ . "\\Views\\Cnab$assignment->cnab\\" + . BankInterchange\Utils::toPascalCase($assignment->bank->name); + + return new $view_class($model); + } + + /** + * Creates a zip file with shipping files and their billets + * + * If $resource is a collection, the generated files are organized in + * directories for each shipping file. + * + * @param Resource $resource Processed MedoolsRouter Resource + * + * @return boolean For success or failure + */ + public static function withBillets(Resource $resource) + { + $controller = new static; + + $list = $resource->getList(); + if (empty($list)) { + return false; + } + + foreach ($list as $id) { + if (!$controller->generate($id)) { + return false; + } + + $title_list = BankInterchange\Models\Title::dump( + ['shipping_file' => $id['id']], + BankInterchange\Models\Title::PRIMARY_KEY + ); + + $bankbillet = new BankInterchange\BankBillet\Controller; + foreach ($title_list as $title_id) { + if (!$bankbillet->generate($title_id)) { + return false; + } + } + $billets = $bankbillet->dump() ?? []; + + if ($resource->kind === 'collection') { + $model = (static::MODEL_CLASS)::getInstance($id); + $directory = "{$model->assignment->id}-$model->counter/"; + + $current = count($controller->views) - 1; + $filename = $controller->views[$current]['name']; + $controller->views[$current]['name'] = $directory . $filename; + + foreach ($billets as $billet_id => $billet) { + $billets[$billet_id]['name'] = $directory . $billet['name']; + } + } + + $controller->views = array_merge($controller->views, $billets); + } + + $controller->zip(); + + return true; + } +} diff --git a/src/ShippingFile/View.php b/src/ShippingFile/View.php new file mode 100644 index 0000000..609c5f7 --- /dev/null +++ b/src/ShippingFile/View.php @@ -0,0 +1,288 @@ +shipping_file = $shipping_file; + + $titles = $shipping_file->getTitles(); + $count = count($titles); + if ($count > static::TITLE_LIMIT) { + throw new \OverflowException(sprintf( + '%s(%s) has %s titles, but only %s are allowed', + get_class($shipping_file), + $shipping_file->id, + $count, + static::TITLE_LIMIT + )); + } + + $this->open(); + foreach ($titles as $title) { + $this->add($title); + } + $this->close(); + } + + /** + * Outputs the file contents in a multiline string + * + * @param string $name File name + * + * @return string If no name is passed + * @return null If a name is passed (result is printed with header) + */ + final public function output(string $name = null) + { + $result = implode(static::EOL, $this->registries) . static::EOL + . static::EOF; + + if ($name === null) { + return $result; + } + + Utils::checkOutput(pathinfo($name)['extension'] ?? ''); + + header('Content-Type: text/plain'); + header('Content-Length: ' . strlen($result)); + header('Content-Disposition: attachment; filename="' . $name . '"'); + echo $result; + } + + /* + * Abstracts + * ========================================================================= + */ + + /** + * Does initial steps for creating a shipping file + */ + abstract protected function open(); + + /** + * Adds a Title registry + * + * @param Models\Title $title Contains data for the registry + */ + abstract protected function add(Models\Title $title); + + /** + * Does final steps for creating a shipping file + */ + abstract protected function close(); + + /* + * FilePack\ViewInterface + * ========================================================================= + */ + + /** + * Generates a filename (without extension) + * + * @return string + */ + public function filename() + { + $shipping_file = $this->shipping_file; + $assignment = $shipping_file->assignment; + + $format = 'COB.%03.3s.%06.6s.%08.8s.%05.5s.%05.5s'; + + $data = [ + $assignment->cnab, + $assignment->edi, + static::date('Ymd', $shipping_file->stamp), + $shipping_file->counter, + $assignment->covenant, + ]; + + return vsprintf($format, $data); + } + + /** + * Returns the View contents + * + * @return string + */ + public function getContents() + { + return $this->output(); + } + + /** + * Outputs the View with appropriated headers + * + * @param string $filename File name to be outputed + */ + public function outputFile(string $filename) + { + $this->output($filename); + } + + /* + * Helper + * ========================================================================= + */ + + /** + * Formats a local time/date from an English textual datetime + * + * NOTE: + * - If $time is empty, the result is '0' + * + * @param string $format @see \date() + * @param mixed $time @see \strtotime() + * + * @return string + */ + protected static function date($format, $time) + { + if (empty($time)) { + return '0'; + } + return date($format, strtotime($time)); + } + + /** + * Remove unwanted characters + * + * @param string $field Field to be filtered + */ + protected static function filter($field) + { + $field = preg_replace('/[\.\/\\:;,?$*!#_-]/', '', $field); + $field = preg_replace('/\s+/', ' ', trim($field)); + return $field; + } + + /** + * Applies a mask in a string + * + * Characters in $subject which position in $mask is MOVEMENT_MASK_CHAR are + * kept, otherwise they are replaced with the corresponding $mask character + * + * NOTE: + * - If $mask is null, it returns $subject unchanged + * - If $mask is an empty string, it returns '' + * + * @param string $subject String to be masked + * @param string $mask Mask to be applied + * + * @return string + */ + protected static function mask($subject, $mask) + { + if ($mask === null) { + return $subject; + } elseif ($mask === '') { + return ''; + } + + $result = str_split($subject); + foreach ($result as $id => $value) { + $char = $mask[$id]; + if ($char !== static::MOVEMENT_MASK_CHAR) { + $result[$id] = $char; + } + } + + return implode('', $result); + } + + /** + * Removes diacritics and convert to upper case + * + * @param string[] $data Data to be normalized + */ + protected static function normalize($data) + { + foreach ($data as $id => $value) { + $data[$id] = strtoupper(NoDiacritic::filter($value)); + } + return $data; + } +} diff --git a/src/ShippingFile/Views/Cnab240.php b/src/ShippingFile/Views/Cnab240.php new file mode 100644 index 0000000..286bbbb --- /dev/null +++ b/src/ShippingFile/Views/Cnab240.php @@ -0,0 +1,148 @@ +registry_count++; + $this->registries[] = $this->generateFileHeader(); + } + + /** + * Adds a Title registry + * + * @param Models\Title $title Contains data for the registry + */ + protected function add(Models\Title $title) + { + if ($this->current_lot === 99999) { + $this->registry_count++; + $this->current_lot += 2; + $this->registries[] = $this->generateLotTrailer(); + $this->current_lot = null; + } + + if ($this->current_lot === null) { + $this->registry_count++; + $this->lot_count++; + $this->current_lot = 0; + $this->registries[] = $this->generateLotHeader(); + } + + $this->registry_count++; + $this->current_lot++; + + $this->registries = array_merge( + $this->registries, + $this->generateLotDetail($title) + ); + } + + /** + * Does final steps for creating a shipping file + */ + protected function close() + { + if ($this->current_lot !== null) { + $this->registry_count++; + $this->current_lot += 2; + $this->registries[] = $this->generateLotTrailer(); + $this->current_lot = null; + } + + $this->registry_count++; + $this->registries[] = $this->generateFileTrailer(); + } + + /* + * Abstracts + * ========================================================================= + */ + + /** + * Generates FileHeader registry + * + * @return string + */ + abstract protected function generateFileHeader(); + + /** + * Generates LotHeader registry + * + * @return string + */ + abstract protected function generateLotHeader(); + + /** + * Generates LotDetail registry + * + * @param Models\Title $title Contains data for the registry + * + * @return string[] + */ + abstract protected function generateLotDetail(Models\Title $title); + + /** + * Generates LotTrailer registry + * + * @return string + */ + abstract protected function generateLotTrailer(); + + /** + * Generates FileTrailer registry + * + * @return string + */ + abstract protected function generateFileTrailer(); +} diff --git a/src/ShippingFile/Views/Cnab240/Banese.php b/src/ShippingFile/Views/Cnab240/Banese.php new file mode 100644 index 0000000..ff98dbb --- /dev/null +++ b/src/ShippingFile/Views/Cnab240/Banese.php @@ -0,0 +1,444 @@ + [ + 'P' => '************** ******************************************* *************** ', + 'Q' => '', + 'R' => '', + ], + '04' => [ + 'P' => '************** ******************************************* *************** *************** ', + 'Q' => '', + 'R' => '', + ], + '05' => [ + 'P' => '************** ******************************************* *************** 000000000000000 ', + 'Q' => '', + 'R' => '', + ], + '06' => [ + 'P' => '************** ******************************************* *********************** ', + 'Q' => '', + 'R' => '', + ], + '07' => [ + 'P' => '************** ******************************************* *************** ************************ ', + 'Q' => '', + 'R' => '************** ************************************************** ', + ], + '08' => [ + 'P' => '************** ******************************************* *************** ************************ ', + 'Q' => '', + 'R' => '************** ************************************************** ', + ], + '12' => [ + 'P' => '************** ******************************************* *************** ************************ ', + 'Q' => '', + 'R' => '', + ], + '13' => [ + 'P' => '************** ******************************************* *************** 300000000000000000000000 ', + 'Q' => '', + 'R' => '', + ], + '14' => [ + 'P' => '', + 'Q' => '', + 'R' => '************** ** ************************ ', + ], + '15' => [ + 'P' => '', + 'Q' => '', + 'R' => '************** ** 000000000000000000000000 ', + ], + '16' => [ + 'P' => '************** ******************************************* *************** *********************** ', + 'Q' => '', + 'R' => '************** ** *********************** *********************** ', + ], + '18' => [ + 'P' => '************** ******************************************* *************** *************** ', + 'Q' => '', + 'R' => '', + ], + '21' => [ + 'P' => '************** ******************************************* *************** *************** ', + 'Q' => '', + 'R' => '', + ], + '31' => [ + 'P' => '************** ******************************************* *********************** ', + 'Q' => '', + 'R' => '', + ], + '42' => [ + 'P' => '************** ******************************************* *************** ** ', + 'Q' => '', + 'R' => '', + ], + ]; + + /** + * Generates FileHeader registry + * + * @return string + */ + protected function generateFileHeader() + { + $shipping_file = $this->shipping_file; + $assignment = $shipping_file->assignment; + $assignor_person = $assignment->assignor->person; + $bank = $assignment->bank; + + $format = '%03.3s%04.4s%01.1s%-9.9s%01.1s%014.14s%020.20s%05.5s%-1.1s' + . '%012.12s%-1.1s%-1.1s%-30.30s%-30.30s%-10.10s%01.1s%08.8s%06.6s' + . '%06.6s%03.3s%05.5s%-20.20s%-20.20s%-29.29s'; + + $data = [ + $bank->code, + '0', + '0', + '', + $assignor_person->getDocumentType(), + $assignor_person->document, + $assignment->covenant, + '0', // $assignment->agency, + '', + '0', // $assignment->account, + '', + '', + Utils::cleanSpaces($assignor_person->name), + $bank->name, + '', + '1', + static::date('dmY', $shipping_file->stamp), + static::date('His', $shipping_file->stamp), + $shipping_file->counter, + static::VERSION_FILE_LAYOUT, + '0', + '', + $shipping_file->notes, + '', + ]; + + return vsprintf($format, static::normalize($data)); + } + + /** + * Generates LotHeader registry + * + * @return string + */ + protected function generateLotHeader() + { + $assignment = $this->shipping_file->assignment; + $assignor_person = $assignment->assignor->person; + $bank = $assignment->bank; + + $format = '%03.3s%04.4s%01.1s%-1.1s%02.2s%-2.2s%03.3s%-1.1s%01.1s' + . '%015.15s%020.20s%05.5s%-1.1s%012.12s%-1.1s%-1.1s%-30.30s%-40.40s' + . '%-40.40s%08.8s%08.8s%08.8s%-33.33s'; + + $data = [ + $bank->code, + $this->lot_count, + '1', + 'R', + '1', + '', + static::VERSION_LOT_LAYOUT, + '', + $assignor_person->getDocumentType(), + $assignor_person->document, + $assignment->covenant, + '0', // $assignment->agency, + '', + '0', // $assignment->account, + '', + '', + Utils::cleanSpaces($assignor_person->name), + '', // message 1 + '', // message 2 + '0', // number shipping/return + '0', // recording date + '0', // credit date + '', + ]; + + return vsprintf($format, static::normalize($data)); + } + + /** + * Generates LotDetail registry + * + * @param Models\Title $title Contains data for the registry + * + * @return string[] + */ + protected function generateLotDetail(Models\Title $title) + { + $result = []; + + $assignment = $title->assignment; + $assignor_person = $assignment->assignor->person; + $bank = $assignment->bank; + $client = $title->client; + $client_address = $client->address; + $client_person = $client->person; + $currency = $title->currency; + $currency_code = $title->getCurrencyCode(); + $guarantor_person = $title->guarantor->person ?? null; + $movement = $title->movement->code; + + $movement_mask = static::MOVEMENT_MASK[$movement] ?? null; + + /* + * 'P' Segment + */ + + $format = '%03.3s%04.4s%01.1s%05.5s%-1.1s%-1.1s%02.2s%05.5s%-1.1s' + . '%012.12s%-1.1s%-1.1s%020.20s%01.1s%01.1s%-1.1s%01.1s%-1.1s' + . '%-15.15s%08.8s%015.15s%05.5s%-1.1s%02.2s%-1.1s%08.8s%01.1s' + . '%08.8s%015.15s%01.1s%08.8s%015.15s%015.15s%015.15s%-25.25s' + . '%01.1s%02.2s%01.1s%03.3s%02.2s%010.10s%-1.1s'; + + $protest_code = $title->protest_code ?? 3; + $protest_days = ($protest_code != 3 ? $title->protest_days ?? 0 : 0); + + $data = [ + $bank->code, + $this->lot_count, + '3', + $this->current_lot, + 'P', + '', + $movement, + '0', // $assignment->agency, + '', + '0', // $assignment->account, + '', + '', + $title->our_number, + $assignment->wallet->code, + '1', // Title's Registration + '1', // Document type + '2', // Emission identifier + '2', // Distribuition identifier + $title->doc_number, + static::date('dmY', $title->due), + $currency->format($title->getActualValue(), 'nomask'), + '0', // Charging agency + '', // Charging agency Check Digit + $title->kind->code, + $title->accept, + static::date('dmY', $title->emission), + $title->interest_type, + static::date('dmY', $title->interest_date), + $currency->format($title->interest_value, 'nomask'), + $title->discount1_type, + static::date('dmY', $title->discount1_date), + $currency->format($title->discount1_value, 'nomask'), + $currency->format($title->ioc_iof, 'nomask'), + $currency->format($title->rebate, 'nomask'), + $title->description, + $protest_code, + min($protest_days, 99), + '1', // down/return code + '0', // down/return due + $currency_code->cnab240, + '0', // Contract number + '1', // Free use: it's defining partial payment isn't allowed + ]; + + $result[] = static::mask( + vsprintf($format, static::normalize($data)), + $movement_mask['P'] + ); + + /* + * 'Q' Segment + */ + + $format = '%03.3s%04.4s%01.1s%05.5s%-1.1s%-1.1s%02.2s%01.1s%015.15s' + . '%-40.40s%-40.40s%-15.15s%08.8s%-15.15s%-2.2s%01.1s%015.15s' + . '%-40.40s%03.3s%020.20s%-8.8s'; + + $client_address_piece = implode(' ', [ + static::filter($client_address->place), + $client_address->number, + static::filter($client_address->detail) + ]); + + $guarantor_document_type = ($guarantor_person !== null) + ? $guarantor_person->getDocumentType() + : ''; + + $data = [ + $bank->code, + $this->lot_count, + '3', + $this->current_lot, + 'Q', + '', + $movement, + $client_person->getDocumentType(), + $client_person->document, + Utils::cleanSpaces($client_person->name), + Utils::cleanSpaces($client_address_piece), + static::filter($client_address->neighborhood), + $client_address->zipcode, + $client_address->county->name, + $client_address->county->state->code, + $guarantor_document_type, + $guarantor_person->document ?? '', + $guarantor_person->name ?? '', + '0', // Corresponding bank + '0', // "Our number" at corresponding bank + '', + ]; + + $result[] = static::mask( + vsprintf($format, static::normalize($data)), + $movement_mask['Q'] + ); + + /* + * 'R' Segment + */ + + if ($title->discount2_type || $title->discount3_type + || $title->fine_type + || in_array($movement, ['07', '08', '14', '15', '16']) + ) { + $format = '%03.3s%04.4s%01.1s%05.5s%-1.1s%-1.1s%02.2s%01.1s%08.8s' + . '%015.15s%01.1s%08.8s%015.15s%01.1s%08.8s%015.15s%-10.10s' + . '%-40.40s%-40.40s%-20.20s%08.8s%03.3s%05.5s%-1.1s%012.12s' + . '%-1.1s%-1.1s%01.1s%-9.9s'; + + $data = [ + $bank->code, + $this->lot_count, + '3', + $this->current_lot, + 'R', + '', + $movement, + $title->discount2_type, + static::date('dmY', $title->discount2_date), + $currency->format($title->discount2_value, 'nomask'), + $title->discount3_type, + static::date('dmY', $title->discount3_date), + $currency->format($title->discount3_value, 'nomask'), + $title->fine_type, + static::date('dmY', $title->fine_date), + $currency->format($title->fine_value, 'nomask'), + '', // information to client + '', // message 3 + '', // message 4 + '', + '0', // client occurence_code + '0', // debit bank_code + '0', // debit agency + '0', // debit agency_cd + '0', // debit account + '', // debit account_cd + '', // debit agency_account_cd + '0', // debit emission_identification + '', + ]; + + $result[] = static::mask( + vsprintf($format, static::normalize($data)), + $movement_mask['R'] + ); + } + + return array_filter($result, 'strlen'); + } + + /** + * Generates LotTrailer registry + * + * @return string + */ + protected function generateLotTrailer() + { + $format = '%03.3s%04.4s%01.1s%-9.9s%06.6s%06.6s%017.17s%06.6s%017.17s' + . '%06.6s%017.17s%06.6s%017.17s%-8.8s%-117.117s'; + + $data = [ + $this->shipping_file->assignment->bank->code, + $this->lot_count, + '5', + '', + $this->current_lot, + '0', // CS + '0', // CS + '0', // CV + '0', // CV + '0', // CC + '0', // CC + '0', // CD + '0', // CD + '', + '', + ]; + + return vsprintf($format, static::normalize($data)); + } + + /** + * Generates FileTrailer registry + * + * @return string + */ + protected function generateFileTrailer() + { + $format = '%03.3s%04.4s%01.1s%-9.9s%06.6s%06.6s%06.6s%-205.205s'; + + $data = [ + $this->shipping_file->assignment->bank->code, + '9999', + '9', + '', + $this->lot_count, + $this->registry_count, + '0', + '', + ]; + + return vsprintf($format, static::normalize($data)); + } +} diff --git a/src/ShippingFile/Views/Cnab400.php b/src/ShippingFile/Views/Cnab400.php new file mode 100644 index 0000000..f92f960 --- /dev/null +++ b/src/ShippingFile/Views/Cnab400.php @@ -0,0 +1,80 @@ +registry_count++; + $this->registries[] = $this->generateHeader(); + } + + /** + * Adds a Title registry + * + * @param Models\Title $title Contains data for the registry + */ + protected function add(Models\Title $title) + { + $this->registry_count++; + $this->registries[] = $this->generateTransaction($title); + } + + /** + * Does final steps for creating a shipping file + */ + protected function close() + { + $this->registry_count++; + $this->registries[] = $this->generateTrailer(); + } + + /* + * Abstracts + * ========================================================================= + */ + + /** + * Generates Header registry + * + * @return string + */ + abstract protected function generateHeader(); + + /** + * Generates Transaction registry + * + * @param Models\Title $title Contains data for the registry + * + * @return string + */ + abstract protected function generateTransaction(Models\Title $title); + + /** + * Generates Trailer registry + * + * @return string + */ + abstract protected function generateTrailer(); +} diff --git a/src/ShippingFile/Views/Cnab400/BancoDoNordeste.php b/src/ShippingFile/Views/Cnab400/BancoDoNordeste.php new file mode 100644 index 0000000..d46edc8 --- /dev/null +++ b/src/ShippingFile/Views/Cnab400/BancoDoNordeste.php @@ -0,0 +1,184 @@ + '* ************** ******** *** ************* ******', + '04' => '* ************** ******** *** ************* ************* ******', + '06' => '* ************** ******** *** ******************* ******', + '07' => '* ************** ********************************* *** ************* ******', + '08' => '* ************** ******** ************* ************* ******', + '09' => '* ************** ******** *** ************* ** ******', + '10' => '* ************** ******** *** ************* 99 ******', + '31' => '* ************** ******** ******************* *** ************* ************* ******************* ************************************************************************************************************************************* ******', + ]; + + /** + * Generates Header registry + * + * @return string + */ + protected function generateHeader() + { + $shipping_file = $this->shipping_file; + $assignment = $shipping_file->assignment; + $assignor_person = $assignment->assignor->person; + $bank = $assignment->bank; + + $format = '%01.1s%01.1s%-7.7s%02.2s%-15.15s%04.4s%02.2s%07.7s%01.1s' + . '%-6.6s%-30.30s%03.3s%-15.15s%06.6s%03.3s%-291.291s%06.6s'; + + $data = [ + '0', + '1', + 'REMESSA', + '1', + 'COBRANCA', + $assignment->agency, + '0', + $assignment->account, + $assignment->account_cd, + '', + Utils::cleanSpaces($assignor_person->name), + $bank->code, + $bank->name, + static::date('dmy', $shipping_file->stamp), + $assignment->edi, + '', + $this->registry_count, + ]; + + return vsprintf($format, static::normalize($data)); + } + + /** + * Generates Transaction registry + * + * @param Models\Title $title Contains data for the registry + * + * @return string + */ + protected function generateTransaction(Models\Title $title) + { + $assignment = $title->assignment; + $assignor_person = $assignment->assignor->person; + $bank = $assignment->bank; + $client = $title->client; + $client_address = $client->address; + $client_person = $client->person; + $currency = $title->currency; + $currency_code = $title->getCurrencyCode(); + $movement = $title->movement->code; + + $format = '%01.1s%-16.16s%04.4s%02.2s%07.7s%01.1s%02.2s%-4.4s%-25.25s' + . '%07.7s%01.1s%010.10s%06.6s%013.13s%-8.8s%01.1s%02.2s%-10.10s' + . '%06.6s%013.13s%03.3s%04.4s%-1.1s%02.2s%-1.1s%06.6s%04.4s%013.13s' + . '%06.6s%013.13s%013.13s%013.13s%02.2s%014.14s%-40.40s%-40.40s' + . '%-12.12s%08.8s%-15.15s%-2.2s%-40.40s%02.2s%-1.1s%06.6s'; + + $client_address_piece = implode(' ', [ + static::filter($client_address->place), + $client_address->number, + static::filter($client_address->neighborhood) + ]); + + $data = [ + '1', + '', + $assignment->agency, + '0', + $assignment->account, + $assignment->account_cd, + '0', // fine percent + '', + $title->doc_number, + $title->our_number, + $title->checkDigitOurNumber(), + '0', // contract + static::date('dmy', $title->discount2_date), + $currency->format($title->discount2_value, 'nomask'), + '', + $assignment->wallet->code, + $movement, + $title->doc_number, + static::date('dmy', $title->due), + $currency->format($title->getActualValue(), 'nomask'), + '0', // Charging bank + '0', // Charging agency + '', + $title->kind->code, + $title->accept, + static::date('dmy', $title->emission), + '5', // instruction code + '0', // interest value + static::date('dmy', $title->discount1_date), + $currency->format($title->discount1_value, 'nomask'), + $currency->format($title->ioc_iof, 'nomask'), + $currency->format($title->rebate, 'nomask'), + $client_person->getDocumentType(), + $client_person->document, + Utils::cleanSpaces($client_person->name), + Utils::cleanSpaces($client_address_piece), + Utils::cleanSpaces($client_address->detail), + $client_address->zipcode, + $client_address->county->name, + $client_address->county->state->code, + '', // message or guarantor name + min($title->protest_days ?? 99, 99), + $currency_code->cnab400, + $this->registry_count, + ]; + + return static::mask( + vsprintf($format, static::normalize($data)), + static::MOVEMENT_MASK[$movement] ?? null + ); + } + + /** + * Generates Trailer registry + * + * @return string + */ + protected function generateTrailer() + { + $format = '%01.1s%-393.393s%06.6s'; + + $data = [ + '9', + '', + $this->registry_count, + ]; + + return vsprintf($format, static::normalize($data)); + } +} diff --git a/src/Utils.php b/src/Utils.php index 49a8dd9..b5ef508 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -1,6 +1,6 @@ $len) { if ($trim) { return substr($result, - $len); @@ -55,4 +70,20 @@ public static function padNumber($val, $len, $trim = false) } return $result; } + + /** + * Converts a string to PascalCase + * + * @param string $string To be converted + * + * @return string + */ + public static function toPascalCase(string $string) + { + return str_replace( + [' ', '_', '.', '-'], + '', + ucwords(NoDiacritic::filter($string), ' _.-') + ); + } } diff --git a/src/Views/BankBillet.php b/src/Views/BankBillet.php deleted file mode 100644 index 26ed5b1..0000000 --- a/src/Views/BankBillet.php +++ /dev/null @@ -1,703 +0,0 @@ - ['Arial', 'B', 8, [0, 0, 0]], - 'digitable1' => ['Arial', 'B', 10, [0, 0, 0]], - 'billhead' => ['Arial', '', 6, [0, 0, 0]], - 'bank_code' => ['Arial', 'B', 14, [0, 0, 0]], - 'cell_title' => ['Arial', '', 6, [0, 0, 51]], - 'cell_data' => ['Arial', 'B', 7, [0, 0, 0]], - 'footer' => ['Arial', '', 9, [0, 0, 0]] - ]; - - /** - * Size of dashes: black, white - * - * @const integer[] - */ - const DASH_STYLE = [2, 1]; - - /** - * Default line width for borders - * - * @const integer - */ - const DEFAULT_LINE_WIDTH = 0.2; - - /** - * Dictionary of terms used in the billet - * - * @var string[] - */ - protected $dictionary = [ - 'accept' => 'Aceite', - 'addition' => '(+) Outros acréscimos', - 'agency_code' => 'Agência/Código do Cedente', - 'amount' => 'Quantidade', - 'assignor' => 'Cedente', - 'bank_use' => 'Uso do banco', - 'charged' => '(=) Valor cobrado', - 'cod_down' => 'Cód. baixa', - 'compensation' => 'Ficha de Compensação', - 'cpf_cnpj' => 'CPF/CNPJ', - 'cut_here' => 'Corte na linha pontilhada', - 'date_due' => 'Vencimento', - 'date_document' => 'Data do documento', - 'date_process' => 'Data processameto', - 'deduction' => '(-) Outras deduções', - 'demonstrative' => 'Demonstrativo', - 'discount' => '(-) Desconto / Abatimentos', - 'doc_number' => 'Número do documento', - 'doc_number_sh' => 'Nº documento', - 'doc_value' => 'Valor documento', - 'doc_value=' => '(=) Valor documento', - 'fine' => '(+) Mora / Multa', - 'guarantor' => 'Sacador/Avalista', - 'header_info' => " Linha Digitável: %s\n Valor: %s", - 'instructions' => 'Instruções (Texto de responsabilidade do cedente)', - 'mech_auth' => 'Autenticação mecânica', - 'our_number' => 'Nosso número', - 'payer' => 'Sacado', - 'payer_receipt' => 'Recibo do Sacado', - 'payment_place' => 'Local de pagamento', - 'specie' => 'Espécie', - 'specie_doc' => 'Espécie doc.', - 'wallet' => 'Carteira' - ]; - - /** - * Holds data from database and manipulates some tables - * - * @var Models\Title - */ - protected $title; - - /** - * Contains extra data for the billet - * - * @var string[] - */ - protected $billet = []; - - /** - * Path to directory with logos - * - * @var string - */ - protected $logos; - - /** - * Contains quick references to $titles' foreign models - * - * @var Model[] - */ - protected $ref = []; - - /** - * Creates a new Billet View object - * - * @param Models\Title $title Holds data for the bank billet - * @param string[] $data Extra data for the bank billet - * @param string $logos Path to directory with logos - */ - public function __construct( - BankI\Models\Title $title, - $data, - $logos - ) { - parent::__construct(); - $this->AliasNbPages('{{ total_pages }}'); - $this->SetLineWidth(static::DEFAULT_LINE_WIDTH); - - $this->title = $title; - $this->billet = $data; - if (file_exists($logos) && is_dir($logos)) { - $this->logos = realpath($logos); - } - - $ref = []; - $ref['assignor'] = $title->assignor; - $ref['assignor.person'] = $ref['assignor']->person; - $ref['assignor.address'] = $ref['assignor']->address; - $ref['bank'] = $ref['assignor']->bank; - $ref['payer'] = $title->payer; - $ref['payer.person'] = $ref['payer']->person; - $ref['payer.address'] = $ref['payer']->address; - $ref['guarantor'] = $title->guarantor; - $ref['guarantor.person'] = $ref['guarantor']->person ?? null; - $ref['specie'] = $title->specie; - $ref['wallet'] = $ref['assignor']->wallet; - $this->ref = $ref; - - $this->beforeDraw(); - $this->drawBillet(); - } - - /* - * Before Drawing / Helper - * ========================================================================= - */ - - /** - * Prepare some data to be used during Draw - */ - protected function beforeDraw() - { - $value = $this->title->value + $this->ref['bank']->tax; - $this->billet['value'] = (float) $value; - - $this->generateBarcode(); - } - - /** - * Calculates Our number's check digit - * - * @return string - */ - protected function checkDigitOurNumber() - { - $our_number = BankI\Utils::padNumber($this->ref['assignor']->agency, 3) - . BankI\Utils::padNumber($this->title->our_number, 8); - - return $this->title->checkDigitOurNumberAlgorithm($our_number); - } - - /** - * Calculates the check digit for Barcode - * - * @param string $code 43 digits - * @return string - */ - protected static function checkDigitBarcode($code) - { - $tmp = Utils\Validation::mod11($code); - - $cd = ($tmp == 0 || $tmp == 1 || $tmp == 10) - ? 1 - : 11 - $tmp; - - return $cd; - } - - /** - * Calculate the amount of days since 1997-10-07 - * - * @return string Four digits - */ - protected function dueFactor() - { - $date = \DateTime::createFromFormat('Y-m-d', $this->title->due); - $epoch = new \DateTime('1997-10-07'); - if ($date && $date > $epoch) { - $diff = substr($date->diff($epoch)->format('%a'), -4); - return str_pad($diff, 4, '0', STR_PAD_LEFT); - } - return '0000'; - } - - /** - * Generates the barcode data and it's digitable line - */ - protected function generateBarcode() - { - $barcode = [ - $this->ref['bank']->code, - $this->ref['specie']->febraban, - '', // Check digit - $this->dueFactor(), - BankI\Utils::padNumber(number_format($this->billet['value'], 2, '', ''), 10), - $this->generateFreeSpace() - ]; - $barcode[2] = self::checkDigitBarcode(implode('', $barcode)); - - $this->billet['barcode'] = implode('', $barcode); - - $this->billet['digitable'] = static::formatDigitable(...$barcode); - } - - /** - * Free space, defined by Bank. - * - * Here: Our number . Agency/Assignor's code - */ - protected function generateFreeSpace() - { - return $this->formatOurNumber(false) . $this->formatAgencyAccount(false); - } - - /* - * Drawing - * ========================================================================= - */ - - /** - * Procedurally draws the bank billet using FPDF methods - */ - protected abstract function drawBillet(); - - /** - * Draws the Page Header - */ - protected function drawPageHeader() - { - $this->billetSetFont('cell_data'); - $this->Cell(177, 3, utf8_decode($this->billet['header_title'] ?? ''), 0, 1, 'C'); - $this->Ln(2); - $this->MultiCell(177, 3, utf8_decode($this->billet['header_body'] ?? '')); - $this->Ln(2); - $this->billetSetFont('digitable'); - $this->MultiCell(177, 3.5, utf8_decode(sprintf( - $this->dictionary['header_info'], - $this->billet['digitable'], - $this->formatMoney($this->billet['value']) - ))); - $this->Ln(4); - } - - /** - * Draws the Billhead - */ - protected function drawBillhead() - { - $this->Ln(2); - $logo = $this->logos - . '/assignors/' - . $this->ref['assignor']->logo; - - if ($this->logos !== null && is_file($logo)) { - $y = $this->GetY(); - $this->Image($logo, null, null, 40, 0, '', $this->ref['assignor']->url); - $y1 = $this->GetY(); - $this->SetXY(50, $y); - } - $this->billetSetFont('billhead'); - $this->MultiCell(103.2, 2.5, utf8_decode($this->ref['assignor.person']->name . "\n" . $this->ref['assignor.person']->documentFormat() . "\n" . $this->ref['assignor.address']->outputLong())); - $this->SetY(max($y1 ?? 0, $this->GetY())); - } - - /** - * Inserts a dashed line, with optional text before or after - * - * The text uses previous font - * - * @param string $text An optional text - * @param boolean $text_first If the text comes first - * @param string $align Aligns the text (L|C|R) - */ - protected function drawDash($text = '', $text_first = false, $align = 'R') - { - $cell = function ($text, $align) { - $this->Cell(177, 4, utf8_decode($text), 0, 1, $align); - }; - - if ($text_first) { - $cell($text, $align); - } - - $this->SetLineWidth(static::DEFAULT_LINE_WIDTH * 0.625); - $y = $this->GetY(); - $this->SetDash(...static::DASH_STYLE); - $this->Line(10, $y, 187, $y); - $this->SetDash(); - $this->SetLineWidth(static::DEFAULT_LINE_WIDTH); - - if (!$text_first) { - $cell($text, $align); - } - } - - /** - * Inserts the Bank header - * - * @param string $digitable_align Aligns the digitable line (L|C|R) - * @param integer $line_width_factor Multiplier for the line width - */ - protected function drawBankHeader( - $digitable_align = 'R', - $line_width_factor = 2 - ) { - $bank = $this->ref['bank']; - $logo = $this->logos . '/banks/' . $bank->logo; - - $this->Ln(3); - if ($this->logos !== null && is_file($logo)) { - $this->Image($logo, null, null, 40); - $this->SetXY(50, $this->GetY() - 7); - } else { - $this->billetSetFont('cell_data'); - $this->Cell(40, 7, utf8_decode($bank->name)); - } - - $this->SetLineWidth(static::DEFAULT_LINE_WIDTH * $line_width_factor); - $this->billetSetFont('bank_code'); - $this->Cell(15, 7, $this->formatBankCode(), 'LR', 0, 'C'); - $this->billetSetFont('digitable1'); - $this->Cell(122, 7, $this->billet['digitable'], 0, 1, $digitable_align); - $y = $this->GetY(); - $this->Line(10, $y, 187, $y); - $this->SetLineWidth(static::DEFAULT_LINE_WIDTH); - } - - /** - * Inserts row of cells - * - * @param mixed[] $cells Data to be written - * @param boolean $close If the border should be closed in the left - */ - protected function drawTableRow($cells, $close = false) - { - $write_row = function ($field, $border) use ($cells, $close) - { - $count = count($cells); - foreach ($cells as $cell) { - $this->Cell($cell['w'], 3.5, utf8_decode($cell[$field]), $border . ($close && $count == 1 ? 'R' : ''), ($count == 1 ? 1 : 0), $cell[$field . '_align'] ?? 'L'); - $count--; - } - }; - $this->billetSetFont('cell_title'); - $write_row('title', 'L'); - $this->billetSetFont('cell_data'); - $write_row('data', 'LB'); - } - - /** - * Inserts column of cells - * - * @param mixed[] $cells Data to be written - * @param integer $x Abscissa offset from page border - * @param integer $w Column width - * @param boolean $close If the border should be closed in the left - */ - protected function drawTableColumn($cells, $x, $w, $close = false) - { - $fields = [ - 'title' => ['font' => 'cell_title', 'border' => 'L'], - 'data' => ['font' => 'cell_data', 'border' => 'LB'] - ]; - foreach ($cells as $cell) { - foreach ($fields as $field => $config) { - $this->SetX($x); - $this->billetSetFont($config['font']); - $this->Cell($w, 3.5, utf8_decode($cell[$field]), $config['border'] . ($close ? 'R' : ''), 1, $cell[$field . '_align'] ?? 'L'); - } - } - } - - /** - * Produces a bar code from string of digits in style "2 of 5 intercalated" - * - * @param string $data Data to be encoded - * @param float $baseline Corresponds to the width of a wide bar - * @param float $height Bar height - * @return - */ - protected function drawBarCode($baseline = 0.8, $height = 13) - { - $data = $this->billet['barcode']; - $wide = $baseline; - $narrow = $baseline / 3; - $map = [ - '00110', - '10001', - '01001', - '11000', - '00101', - '10100', - '01100', - '00011', - '10010', - '01010' - ]; - - // If odd $data - if ((strlen($data) % 2) != 0) { - $data = '0' . $data; - } - // Generate bits to be draw - $code = '0000'; // Leading value - for ($i = 0, $l = strlen($data); $i < $l; $i += 2) { - $code .= implode('', Utils\Utils::arrayInterpolate($map[$data[$i]], $map[$data[$i + 1]])); - } - $code .= '100'; // Trailing value - - // Draw - $this->SetFillColor(0); - $x = $this->GetX(); - $y = $this->GetY(); - $draw = true; - foreach (str_split($code, 1) as $bit) { - $width = ($bit == '0') - ? $narrow - : $wide; - if ($draw) { - $this->Rect($x, $y, $width, $height, 'F'); - } - $x += $width; - $draw = !$draw; - } - $this->Ln($height); - } - - /* - * Formatting - * ========================================================================= - */ - - /** - * Formats Assignor's Agency/Account - * - * @param boolean $symbol If shoud include symbols - * - * @return string - */ - protected function formatAgencyAccount($symbol = true) - { - return $this->ref['assignor']->formatAgencyAccount( - static::AGENCY_LENGTH, - static::ACCOUNT_LENGTH, - $symbol - ); - } - - /** - * Calculates Bank code's check digit and formats it - * - * @return string - */ - protected function formatBankCode() - { - $code = $this->ref['bank']->code; - - $checksum = Utils\Validation::mod11Pre($code); - $digit = $checksum * 10 % 11; - if ($digit == 10) { - $digit = 0; - } - - return $code . '-' . $digit; - } - - /** - * Formats a date from Y-m-d to d/m/Y - * - * @param string $date Date string - * - * @return string - */ - protected static function formatDate($date) - { - $d = \DateTime::createFromFormat('Y-m-d', $date); - if (!$d) { - $d = \DateTime::createFromFormat('Y-m-d H:i:s', $date); - } - return ($d ? $d->format('d/m/Y') : $date); - } - - /** - * Formats the barcode into a Digitable line - * - * @param string $bank Bank code (3 digits) - * @param string $specie Specie Code (1 digit) - * @param string $cd Check digit (1 digit) - * @param string $due Due Factor (4 digits) - * @param string $value Document Value (10 digits: 8 integers and 2 decimals) - * @param string $free Free space (25 digits, defined by each bank) - * @return string - */ - protected static function formatDigitable( - $bank_code, - $specie_code, - $check_digit, - $due_factor, - $value, - $free_space - ) { - $fields = []; - - /* - * Field #0 - * - * - $bank_code - * - $specie_code - * - 5 first digits from $free_space - * - Check digit for this field - */ - $tmp = $bank_code . $specie_code . substr($free_space, 0, 5); - $tmp .= Utils\Validation::mod10($tmp); - $fields[] = implode('.', str_split($tmp, 5)); - - /* - * Field #1 - * - * - Digits 6 to 15 from $free_space - * - Check digit for this field - */ - $tmp = substr($free_space, 5, 10); - $fields[] = implode('.', str_split($tmp, 5)) - . Utils\Validation::mod10($tmp); - - /* - * Field #2 - * - * - Digits 16 to 25 from $free_space - * - Check digit for this field - */ - $tmp = substr($free_space, 15, 10); - $fields[] = implode('.', str_split($tmp, 5)) - . Utils\Validation::mod10($tmp); - - /* - * Field #3 - * - * - Digitable line $check_digit - */ - $fields[] = $check_digit; - - /* - * Field #4 - * - * - $due_factor - * - Document $value - */ - $fields[] = $due_factor . $value; - - return implode(' ', $fields); - } - - /** - * Formats a numeric value as monetary value - * - * @param number $value Value to be formated - * @param string $format @see Models/Specie::format() - * - * @return string - */ - protected function formatMoney($value, $format = 'symbol') - { - return $this->ref['specie']->format($value, $format); - } - - /** - * Calculates Our number's check digit and formats it - * - * @param boolean $dash If should add a dash between number and check digit - * - * @return string - */ - protected function formatOurNumber($dash = true) - { - $our_number = BankI\Utils::padNumber( - $this->title->our_number, - static::OUR_NUMBER_LENGTH - ); - - $result = $our_number - . ($dash ? '-' : '') - . $this->checkDigitOurNumber(); - - return $result; - } - - /* - * Internal - * ========================================================================= - */ - - /** - * Allows an easy way to set current font - * - * @param string $font Key for FONTS - */ - protected function billetSetFont($font) - { - $f = static::FONTS[$font]; - $this->SetFont($f[0], $f[1], $f[2]); - if (count($f) > 3) { - $this->SetTextColor(...$f[3]); - } - } - - /** - * This extension allows to set a dash pattern and draw dashed lines or rectangles. - * - * Call the function without parameter to restore normal drawing. - * - * @link http://www.fpdf.org/en/script/script33.php - * @author yukihiro_o - * @license FPDF - * @param float black Length of dashes - * @param float white Length of gaps - */ - protected function SetDash($black = null, $white = null) - { - if ($black !== null) { - $s = sprintf('[%.3F %.3F] 0 d', $black * $this->k, $white * $this->k); - } else { - $s = '[] 0 d'; - } - $this->_out($s); - } - - /** - * Page footer - * - * Used internally by FPDF - */ - public function Footer() - { - $this->SetY(-15); - $this->billetSetFont('footer'); - $this->Cell(88.5, 5, $this->PageNo() . ' / {{ total_pages }}'); - $this->Cell(88.5, 5, date('Y-m-d H:i:s O'), 0, 0, 'R'); - } -} diff --git a/src/Views/BankBillets/BancoDoNordeste.php b/src/Views/BankBillets/BancoDoNordeste.php deleted file mode 100644 index 3aab9f8..0000000 --- a/src/Views/BankBillets/BancoDoNordeste.php +++ /dev/null @@ -1,60 +0,0 @@ -formatAgencyAccount(false) - . $this->formatOurNumber(false) - . '21' - . '000'; - return $result; - } - - /** - * Prepare some data to be used during Draw - * - * @param mixed[] $data Data for the bank billet - */ - protected function beforeDraw() - { - $this->ref['wallet']->symbol = $this->ref['wallet']->operation; - parent::beforeDraw(); - } -} diff --git a/src/Views/BankBillets/Banese.php b/src/Views/BankBillets/Banese.php deleted file mode 100644 index bcfb7bf..0000000 --- a/src/Views/BankBillets/Banese.php +++ /dev/null @@ -1,290 +0,0 @@ - ['Arial', 'B', 8, [ 0, 0, 0]], - 'digitable1' => ['Arial', 'B', 10, [ 0, 0, 0]], - 'billhead' => ['Arial', '', 6, [ 0, 0, 0]], - 'bank_code' => ['Arial', 'B', 9, [ 0, 0, 0]], - 'cell_title' => ['Arial', '', 6, [20, 20, 20]], - 'cell_data' => ['Arial', 'B', 7, [ 0, 0, 0]], - 'footer' => ['Arial', '', 9, [ 0, 0, 0]] - ]; - - /** - * Size of dashes: black, white - * - * @const integer[] - */ - const DASH_STYLE = [0.625, 0.75]; - - /** - * Default line width for borders - * - * @const integer - */ - const DEFAULT_LINE_WIDTH = 0.3; - - /** - * Free space: Asbace key - * - * Here: Agency . Account . Our number . Bank code . 2 check digits - */ - protected function generateFreeSpace() - { - $key = BankI\Utils::padNumber($this->ref['assignor']->agency, 2, true) - . BankI\Utils::padNumber($this->ref['assignor']->account, 9, true) - . $this->formatOurNumber(false) - . BankI\Utils::padNumber($this->ref['bank']->code, 3, true); - $cd1 = Validation::mod10($key); - $cd2 = Validation::mod11($key . $cd1, 7); - - if ($cd2 == 1) { - if ($cd1 < 9) { - $cd1++; - $cd2 = Validation::mod11($key . $cd1, 7); - } elseif ($cd1 == 9) { - $cd1 = 0; - $cd2 = Validation::mod11($key . $cd1, 7); - } - } elseif ($cd2 > 1) { - $cd2 = 11 - $cd2; - } - - return $key . $cd1 . $cd2; - } - - /** - * Procedurally draws the bank billet using FPDF methods - */ - protected function drawBillet() - { - $this->dictionary = array_replace( - $this->dictionary, - [ - 'assignor' => 'Beneficiário', - 'agency_code' => 'Agência/Cod. Beneficiário', - 'date_process' => 'Data do processameto', - 'doc_number_sh' => 'Nº do documento', - 'discount' => '(-) Desconto/ Abatimento', - 'doc_value' => 'Valor', - 'doc_value=' => '(=) Valor do documento', - 'fine' => '(+) Mora/Multa', - 'guarantor' => 'Sacador/Avalista: ', - 'instructions' => 'Instruções', - 'payer' => 'Pagador', - 'payer_receipt' => 'Recibo do Pagador', - 'specie' => 'Moeda', - 'specie_doc' => 'Espécie doc', - ] - ); - $keys = [ - 'accept', 'addition', 'agency_code', 'amount', 'assignor', - 'bank_use', 'charged', 'date_due', 'date_document', 'date_process', - 'deduction', 'demonstrative', 'discount', 'doc_number_sh', - 'doc_value', 'doc_value=', 'fine', 'guarantor', 'instructions', - 'mech_auth', 'our_number', 'payer', 'payment_place', 'specie', - 'specie_doc', 'wallet' - ]; - foreach ($keys as $key) { - $this->dictionary[$key] = mb_strtoupper($this->dictionary[$key]); - } - - $dict = $this->dictionary; - - $this->AddPage(); - - $this->drawPageHeader(); - - $this->billetSetFont('cell_data'); - $this->drawDash($dict['payer_receipt']); - - $this->drawBillhead(); - - $this->drawBankHeader('L', 1); - - $this->drawTable('demonstrative'); - - $this->Ln(4); - - $this->billetSetFont('cell_title'); - $this->drawDash($dict['compensation']); - - $this->SetY($this->GetY() - 3); - - $this->drawBankHeader('L', 1); - - $this->drawTable('instructions'); - - $this->SetY($this->GetY() - 3); - $this->drawBarCode(); - } - - /** - * Generic Table - * - * NOTES: - * - '{{ tax }}' is replaced by the money-formated tax in the big cell, if - * setted to demonstrative - * - * @param string $big_cell demonstrative|instructions Tells which information - * goes in the big cell - */ - protected function drawTable($big_cell = 'instructions') - { - $dict = $this->dictionary; - $title = $this->title; - $assignor = $this->ref['assignor']; - $assignor_person = $this->ref['assignor.person']; - $bank = $this->ref['bank']; - $payer_person = $this->ref['payer.person']; - $wallet = $this->ref['wallet']; - - $y = $this->GetY(); // get Y to come back and add the aside column - - /* - * Structure: - * - * Payment place - * Assignor - * Document Date | Document number | Document specie | Accept | Processing Date - * Bank's use | Wallet | Specie | Amount | Document value UN - * Payer - */ - $table = [ - [ - ['w' => 127.2, 'title' => $dict['payment_place'], 'data' => $this->billet['payment_place'] ?? ''] - ], - [ - ['w' => 127.2, 'title' => $dict['assignor'], 'data' => $assignor_person->name . ' ' . $assignor_person->documentFormat(true)] - ], - [ - ['w' => 32, 'title' => $dict['date_document'], 'data' => self::formatDate($title->stamp)], - ['w' => 27, 'title' => $dict['doc_number_sh'], 'data' => BankI\Utils::padNumber($title->id, 10)], - ['w' => 20, 'title' => $dict['specie_doc'], 'data' => '5'], //$data['misc']['specie_doc'] - ['w' => 12, 'title' => $dict['accept'], 'data' => 'A'], //$data['misc']['accept'] - ['w' => 36.2, 'title' => $dict['date_process'], 'data' => date('d/m/Y')] - ], - [ - ['w' => 32, 'title' => $dict['bank_use'], 'data' => ''], //$data['misc']['bank_use'] - ['w' => 16, 'title' => $dict['wallet'], 'data' => $wallet->symbol], - ['w' => 11, 'title' => $dict['specie'], 'data' => $this->ref['specie']->symbol], - ['w' => 32, 'title' => $dict['amount'], 'data' => ''], //$data['misc']['amount'] - ['w' => 36.2, 'title' => $dict['doc_value'], 'data' => ''] //$data['misc']['value_un'] - ] - ]; - foreach ($table as $row) { - $this->drawTableRow($row); - } - - // Big cell: Instructions or Demonstrative - $big_cell_text = $this->billet[$big_cell] ?? ''; - if ($big_cell == 'demonstrative') { - $big_cell_text = str_replace('{{ tax }}', $this->formatMoney($bank->tax), $big_cell_text); - } - $y1 = $this->GetY(); - $this->billetSetFont('cell_title'); - $this->Cell(127.2, 7, utf8_decode($dict[$big_cell]), 0, 1); - $this->billetSetFont('cell_data'); - $this->MultiCell(127.2, 3.5, utf8_decode($big_cell_text)); - $y2 = $this->GetY(); - - /** - * Aside column: - * - * Structure: - * - * Due - * Agency/Assignor's code - * Our number - * (=) Document value - * (-) Discount/Rebates - * (-) Other deductions - * (+) "Mora"/Fine - * (+) Other additions - * (=) Amount charged - */ - $this->SetY($y); - $table = [ - ['title' => $dict['date_due'], 'data' => self::formatDate($title->due), 'data_align' => 'R'], - ['title' => $dict['agency_code'], 'data' => $this->formatAgencyAccount(), 'data_align' => 'R'], - ['title' => $dict['our_number'], 'data' => $this->formatOurNumber(), 'data_align' => 'R'], - ['title' => $dict['doc_value='], 'data' => $this->formatMoney($this->billet['value']), 'data_align' => 'R'], - ['title' => $dict['discount'], 'data' => '', 'data_align' => 'R'], //$data['misc']['discount'] - ['title' => $dict['deduction'], 'data' => '', 'data_align' => 'R'], //$data['misc']['deduction'] - ['title' => $dict['fine'], 'data' => '', 'data_align' => 'R'], //$data['misc']['fine'] - ['title' => $dict['addition'], 'data' => '', 'data_align' => 'R'], //$data['misc']['addition'] - ['title' => $dict['charged'], 'data' => '', 'data_align' => 'R'] //$data['misc']['charged'] - ]; - $this->drawTableColumn($table, 137.2, 49.8, true); - - // Instructions border - $y = $this->GetY(); - $y3 = max($y, $y2); - $this->Line(10, $y1, 10, $y3); - $this->Line(10, $y3, 137.2, $y3); - if ($y3 > $y) { - $this->Line(137.2, $y3, 137.2, $y); - $this->SetY($y3); - } - - // Payer - $y = $this->GetY(); - $this->billetSetFont('cell_title'); - $this->Cell(10, 3.5, $dict['payer']); - $this->SetXY($this->GetX() + 5, $y); - $this->billetSetFont('cell_data'); - $this->MultiCell(112.2, 3.5, utf8_decode($payer_person->name . "\n" . $this->ref['payer.address']->outputLong())); - $y1 = $this->GetY(); - $this->SetXY(119.2, $y); - $this->Cell(36, 3.5, $payer_person->documentFormat(true), 0, 0, 'C'); - $this->setY($y1); - - // Guarantor - - $guarantor = ($this->ref['guarantor'] !== null) - ? $this->ref['guarantor.person']->name . ' ' - . $this->ref['guarantor.address']->outputShort() - : ''; - $this->billetSetFont('cell_title'); - $this->Cell(24, 3.5, $dict['guarantor']); - $this->billetSetFont('cell_data'); - $this->Cell(153, 3.5, utf8_decode($guarantor), 0, 1); - $x = $this->GetX(); - $y1 = $this->GetY(); - - // Payer / Guarantor border - $this->Line($x, $y, $x, $y1); - $this->Line($x, $y1, 187, $y1); - $this->Line(187, $y, 187, $y1); - - // Mechanical authentication - $this->SetX(119.2); - $this->billetSetFont('cell_title'); - $this->Cell(67.8, 3.5, utf8_decode($dict['mech_auth'] . '/' . $wallet->name)); - $this->Ln(3.5); - } -} diff --git a/src/Views/BankBillets/CaixaEconomicaFederal.php b/src/Views/BankBillets/CaixaEconomicaFederal.php deleted file mode 100644 index ba6835e..0000000 --- a/src/Views/BankBillets/CaixaEconomicaFederal.php +++ /dev/null @@ -1,233 +0,0 @@ -dictionary; - - $this->AddPage(); - - $this->drawPageHeader(); - - $this->billetSetFont('cell_data'); - $this->drawDash($dict['payer_receipt']); - - $this->drawBillhead(); - - $this->drawBankHeader(); - - $this->drawTable1(); - - $this->billetSetFont('cell_title'); - $this->drawDash($dict['cut_here'], true); - - $this->drawBankHeader(); - - $this->drawTable2(); - - $this->drawBarCode(); - - $this->billetSetFont('cell_title'); - $this->drawDash($dict['cut_here'], true); - } - - /** - * Table 1, stays with the Payer - * - * NOTES: - * - '{{ tax }}' is replaced by the money-formated tax in the demonstrative - */ - protected function drawTable1() - { - $dict = $this->dictionary; - $title = $this->title; - $assignor = $this->ref['assignor']; - $assignor_person = $this->ref['assignor.person']; - $bank = $this->ref['bank']; - $payer_person = $this->ref['payer.person']; - $wallet = $this->ref['wallet']; - - /* - * Structure: - * - * Assignor | Agency/Assignor's code | Specie | Amount | Our number - * Document number | CPF/CNPJ | Due | Document value - * (-) Discount/Rebates | (-) Other deductions | (+) "Mora"/Fine | (+) Other additions | (=) Amount charged - * Payer - */ - $table = [ - [ - ['w' => 80.8, 'title' => $dict['assignor'], 'data' => $assignor_person->name], - ['w' => 35.4, 'title' => $dict['agency_code'], 'data' => $this->formatAgencyAccount()], - ['w' => 11, 'title' => $dict['specie'], 'data' => $this->ref['specie']->symbol], - ['w' => 16, 'title' => $dict['amount'], 'data' => ''], // $data['misc']['amount'] - ['w' => 33.8, 'title' => $dict['our_number'], 'data' => $this->formatOurNumber(), 'data_align' => 'R'] - ], - [ - ['w' => 52.8, 'title' => $dict['doc_number'], 'data' => BankI\Utils::padNumber($title->id, 10)], - ['w' => 37, 'title' => $dict['cpf_cnpj'], 'data' => $assignor_person->documentFormat()], - ['w' => 37.4, 'title' => $dict['date_due'], 'data' => self::formatDate($title->due)], - ['w' => 49.8, 'title' => $dict['doc_value'], 'data' => $this->formatMoney($this->billet['value']), 'data_align' => 'R'] - ], - [ - ['w' => 32, 'title' => $dict['discount'], 'data' => '', 'data_align' => 'R'], //$data['misc']['discount'] - ['w' => 32, 'title' => $dict['deduction'], 'data' => '', 'data_align' => 'R'], //$data['misc']['deduction'] - ['w' => 32, 'title' => $dict['fine'], 'data' => '', 'data_align' => 'R'], //$data['misc']['fine'] - ['w' => 31.2, 'title' => $dict['addition'], 'data' => '', 'data_align' => 'R'], //$data['misc']['addition'] - ['w' => 49.8, 'title' => $dict['charged'], 'data' => '', 'data_align' => 'R'] //$data['misc']['charged'] - ], - [ - ['w' => 177, 'title' => $dict['payer'], 'data' => $payer_person->name] - ] - ]; - foreach ($table as $row) { - $this->drawTableRow($row); - } - - // Demonstrative ('{{ tax }}' is replaced by the money-formated tax) - $this->billetSetFont('cell_title'); - $this->Cell(151, 3.5, $dict['demonstrative'], 0, 0); - $this->Cell(26, 3.5, utf8_decode($dict['mech_auth']), 0, 1); - $this->billetSetFont('cell_data'); - $y = $this->GetY(); - $this->MultiCell(151, 3.5, utf8_decode(str_replace('{{ tax }}', $this->formatMoney($bank->tax), $this->billet['demonstrative'] ?? ''))); - $y1 = $this->GetY(); - $this->SetXY(161, $y); - $this->Cell(26, 3.5, '', 0, 1); - $y2 = $this->GetY(); - $this->SetY(max($y + 14, $y1, $y2)); - $this->Ln(12); - } - - /** - * Table 2, stays in payment place - */ - protected function drawTable2() - { - $dict = $this->dictionary; - $title = $this->title; - $assignor = $this->ref['assignor']; - $assignor_person = $this->ref['assignor.person']; - $bank = $this->ref['bank']; - $payer_person = $this->ref['payer.person']; - $wallet = $this->ref['wallet']; - - $y = $this->GetY(); // get Y to come back and add the aside column - - /* - * Structure: - * - * Payment place - * Assignor - * Document Date | Document number | Document specie | Accept | Processing Date - * Bank's use | Wallet | Specie | Amount | Document value UN - */ - $table = [ - [ - ['w' => 127.2, 'title' => $dict['payment_place'], 'data' => $this->billet['payment_place'] ?? ''] - ], - [ - ['w' => 127.2, 'title' => $dict['assignor'], 'data' => $assignor_person->name] - ], - [ - ['w' => 32, 'title' => $dict['date_document'], 'data' => self::formatDate($title->stamp)], - ['w' => 42.2, 'title' => $dict['doc_number_sh'], 'data' => BankI\Utils::padNumber($title->id, 10)], - ['w' => 18, 'title' => $dict['specie_doc'], 'data' => static::SPECIE_DOC], //$data['misc']['specie_doc'] - ['w' => 11, 'title' => $dict['accept'], 'data' => ''], //$data['misc']['accept'] - ['w' => 24, 'title' => $dict['date_process'], 'data' => date('d/m/Y')] - ], - [ - ['w' => 32, 'title' => $dict['bank_use'], 'data' => ''], //$data['misc']['bank_use'] - ['w' => 24, 'title' => $dict['wallet'], 'data' => $wallet->symbol], - ['w' => 16, 'title' => $dict['specie'], 'data' => $this->ref['specie']->symbol], - ['w' => 34.2, 'title' => $dict['amount'], 'data' => ''], //$data['misc']['amount'] - ['w' => 21, 'title' => $dict['doc_value'], 'data' => ''] //$data['misc']['value_un'] - ] - ]; - foreach ($table as $row) { - $this->drawTableRow($row); - } - - // Instructions - $y1 = $this->GetY(); - $this->billetSetFont('cell_title'); - $this->Cell(127.2, 7, utf8_decode($dict['instructions']), 0, 1); - $this->billetSetFont('cell_data'); - $this->MultiCell(127.2, 3.5, utf8_decode($this->billet['instructions'] ?? '')); - $y2 = $this->GetY(); - - /** - * Aside column: - * - * Structure: - * - * Due - * Agency/Assignor's code - * Our number - * (=) Document value - * (-) Discount/Rebates - * (-) Other deductions - * (+) "Mora"/Fine - * (+) Other additions - * (=) Amount charged - */ - $this->SetY($y); - $table = [ - ['title' => $dict['date_due'], 'data' => self::formatDate($title->due), 'data_align' => 'R'], - ['title' => $dict['agency_code'], 'data' => $this->formatAgencyAccount(), 'data_align' => 'R'], - ['title' => $dict['our_number'], 'data' => $this->formatOurNumber(), 'data_align' => 'R'], - ['title' => $dict['doc_value='], 'data' => $this->formatMoney($this->billet['value']), 'data_align' => 'R'], - ['title' => $dict['discount'], 'data' => '', 'data_align' => 'R'], //$data['misc']['discount'] - ['title' => $dict['deduction'], 'data' => '', 'data_align' => 'R'], //$data['misc']['deduction'] - ['title' => $dict['fine'], 'data' => '', 'data_align' => 'R'], //$data['misc']['fine'] - ['title' => $dict['addition'], 'data' => '', 'data_align' => 'R'], //$data['misc']['addition'] - ['title' => $dict['charged'], 'data' => '', 'data_align' => 'R'] //$data['misc']['charged'] - ]; - $this->drawTableColumn($table, 137.2, 49.8); - - // Instructions border - $y = $this->GetY(); - $y3 = max($y, $y2); - $this->Line(10, $y1, 10, $y3); - $this->Line(10, $y3, 137.2, $y3); - if ($y3 > $y) { - $this->Line(137.2, $y3, 137.2, $y); - $this->SetY($y3); - } - - // Payer - $this->billetSetFont('cell_title'); - $this->Cell(127.2, 7, $dict['payer'], 'L', 1); - $this->billetSetFont('cell_data'); - $this->MultiCell(127.2, 3.5, utf8_decode($payer_person->name . "\n" . $this->ref['payer.address']->outputLong()), 'LB'); - $this->SetXY(137.2, $this->GetY() - 3.5); - $this->billetSetFont('cell_title'); - $this->Cell(49.8, 3.5, utf8_decode($dict['cod_down']), 'LB', 1); - - // Guarantor - $this->Cell(110, 3.5, $dict['guarantor']); - $this->Cell(39.5, 3.5, utf8_decode($dict['mech_auth'] . ' - '), 0, 0, 'R'); - $this->billetSetFont('cell_data'); - $this->Cell(27.5, 3.5, utf8_decode($dict['compensation']), 0, 1, 'R'); - } -} diff --git a/src/Views/Cnab.php b/src/Views/Cnab.php deleted file mode 100644 index 807384a..0000000 --- a/src/Views/Cnab.php +++ /dev/null @@ -1,213 +0,0 @@ -shipping_file = $shipping_file; - - $title_list = BankI\Models\ShippingFileTitle::dump([ - 'shipping_file' => $shipping_file->id - ]); - $title_list = array_column($title_list, 'title'); - - $shipping_file_titles = new Medools\ModelIterator( - 'aryelgois\BankInterchange\Models\Title', - ['id' => $title_list] - ); - - $this->open(); - foreach ($shipping_file_titles as $title) { - $this->add($title); - } - $this->close(); - } - - /** - * Outputs the file contents in a multiline string - * - * @return string - */ - final public function output() - { - return implode(static::LINE_END, $this->registries) - . static::LINE_END . static::FILE_END; - } - - /** - * ... - * - * @return string - */ - final public function filename($cnab) - { - $assignor = $this->shipping_file->assignor; - - $format = 'COB.%03.3s.%06.6s.%08.8s.%05.5s.%05.5s.REM'; - - $data = [ - $cnab, - $assignor->edi, - date('Ymd', strtotime($this->shipping_file->stamp)), - $this->shipping_file->id, - $assignor->covenant, - ]; - - return sprintf($format, ...$data); - } - - /* - * Main methods - * ========================================================================= - */ - - /** - * Adds a File Header - */ - protected function open() - { - $this->registry_count++; - $this->addFileHeader(); - } - - /** - * Adds a Title registry - * - * @param Models\Title $title Contains data for the registry - * - * @throws OverflowException If there are too many registries - */ - protected function add(BankI\Models\Title $title) - { - $this->increment(999998); - $this->addTitle($title); - } - - /** - * Actually inserts the registry in $this->registries - * - * @param string $format A sprintf() format - * @param string[] $data Registry data to be inserted - */ - final protected function register($format, $data) - { - // data cleanup - foreach ($data as $id => $value) { - $data[$id] = strtoupper(NoDiacritic::filter($value)); - $data[$id] = preg_replace('/:;,\.\/\\\?\$\*!#_-/', '', $data[$id]); - } - - $this->registries[] = sprintf($format, ...$data); - } - - /** - * Adds a File Trailer - */ - protected function close() - { - $this->increment(999999); - $this->addFileTrailer(); - } - - /* - * Abstracts - * ========================================================================= - */ - - /** - * ... - */ - abstract protected function addFileHeader(); - - /** - * ... - * - * @param Models\Title $title ... - */ - abstract protected function addTitle(BankI\Models\Title $title); - - /** - * ... - */ - abstract protected function addFileTrailer(); - - /* - * Helper - * ========================================================================= - */ - - /** - * Increments the Registries counter - * - * @param integer $limit Defines the overflow limit - * - * @throws \OverflowException If there are too many registries - */ - protected function increment($limit) - { - if ($this->registry_count > $limit) { - throw new \OverflowException('The File got too many registries'); - } - $this->registry_count++; - } -} diff --git a/src/Views/Cnabs/Cnab240.php b/src/Views/Cnabs/Cnab240.php deleted file mode 100644 index 0dfd7da..0000000 --- a/src/Views/Cnabs/Cnab240.php +++ /dev/null @@ -1,395 +0,0 @@ -openLot(); - } - - /** - * Starts a new Lot - * - * @throws \OverflowException If the file has too many Lots - */ - protected function openLot() - { - if ($this->lot >= 9998) { - throw new \OverflowException('The File got too many Lots'); - } elseif ($this->lot > 0) { - $this->closeLot(); - } - - $this->lots[++$this->lot] = [ - 'registries' => 1, // Already counting the - 'lines' => 1, // following LotHeader - 'titles' => 0, - 'total' => 0.0, - 'closed' => false - ]; - - $this->addLotHeader(); - $this->increment(999997); // I think - } - - /** - * Adds a new Title entry - * - * @param Title $title What the entry is about - * - * @return boolean For success or failure - * - * @throws \OverflowException If there are too many lot registries - */ - protected function addTitle(BankI\Models\Title $title) - { - // Check if the file or the current log is closed - if ($this->lots[$this->lot]['closed']) { - return false; - } - - // Check if the Lot is full - /** - * @todo change those big numbers to be 999999 - the amount of lines - * to be added - */ - $count = $this->lots[$this->lot]['registries']; - if ($count > 999998) { - throw new \OverflowException('The Lot got too many registries'); - } elseif ($count == 999998) { - $this->openLot(); - } - - $this->addLotDetail($title); - - $this->lots[$this->lot]['registries']++; - $this->lots[$this->lot]['lines'] += 2; - $this->lots[$this->lot]['titles']++; - $this->lots[$this->lot]['total'] += $title->value; - $this->registry_count += 2; // the amount of segments (type 3) - - return true; - } - - /** - * Adds a Lot Trailer if the current lot is open - */ - protected function closeLot() - { - if (!$this->lots[$this->lot]['closed']) { - $this->lots[$this->lot]['registries']++; - $this->lots[$this->lot]['lines']++; - $this->addLotTrailer(); - $this->registry_count++; - $this->lots[$this->lot]['closed'] = true; - } - } - - /** - * Adds a File Trailer - */ - protected function close() - { - $this->closeLot(); - $this->lot = 9999; - $this->registry_count++; - $this->addFileTrailer(); - } - - /* - * Internals - * ========================================================================= - */ - - /** - * Adds a File Header - */ - protected function addFileHeader() - { - $assignor = $this->shipping_file->assignor; - $assignor_person = $assignor->person; - $bank = $assignor->bank; - - $format = '%03.3s%04.4s%01.1s%-9.9s%01.1s%014.14s%020.20s%05.5s%-1.1s' - . '%012.12s%-1.1s%-1.1s%-30.30s%-30.30s%-10.10s%01.1s%08.8s' - . '%06.6s%06.6s%03.3s%05.5s%-20.20s%-20.20s%-29.29s'; - - $data = [ - $bank->code, - $this->lot, - '0', - '', - $assignor_person->documentValidate()['type'], - $assignor_person->document, - $assignor->covenant, - $assignor->agency, - '', - $assignor->account, - $assignor->account_cd, - '', - $assignor_person->name, - $bank->name, - '', - '1', - date('dmY'), - date('His'), - $this->shipping_file->id, - self::VERSION_FILE_LAYOUT, - '00000', - '', - '', - '', - ]; - - $this->register($format, $data); - } - - /** - * Adds a Lot Header - */ - protected function addLotHeader() - { - $assignor = $this->shipping_file->assignor; - $assignor_person = $assignor->person; - $bank = $assignor->bank; - - $format = '%03.3s%04.4s%01.1s%-1.1s%02.2s%-2.2s%03.3s%-1.1s%01.1s' - . '%015.15s%020.20s%05.5s%-1.1s%012.12s%-1.1s%-1.1s%-30.30s' - . '%-40.40s%-40.40s%08.8s%08.8s%08.8s%-33.33s'; - - $data = [ - $bank->code, - $this->lot, - '1', - 'R', - '1', - '', - self::VERSION_LOT_LAYOUT, - '', - $assignor_person->documentValidate()['type'], - $assignor_person->document, - $assignor->covenant, - $assignor->agency, - '', - $assignor->account, - $assignor->account_cd, - '', - $assignor_person->name, - '', // message 1 - '', // message 2 - '0', // number shipping/return - '0', // recording date - '0', // credit date - '', - ]; - - $this->register($format, $data); - } - - /** - * Adds a Lot Detail - * - * @param Title $title Holds data about the title and the related payer - * @param integer $movement ... - */ - protected function addLotDetail(BankI\Models\Title $title, $movement = 1) - { - $assignor = $title->assignor; - $assignor_person = $assignor->person; - $bank = $assignor->bank; - $payer = $title->payer; - $payer_person = $payer->person; - $payer_address = $payer->address; - $guarantor_person = $title->guarantor->person ?? null; - - /* - * 'P' Segment - */ - - $format = '%03.3s%04.4s%01.1s%05.5s%-1.1s%-1.1s%02.2s%05.5s%-1.1s' - . '%012.12s%-1.1s%-1.1s%020.20s%01.1s%01.1s%-1.1s%01.1s%-1.1s' - . '%-15.15s%08.8s%015.15s%05.5s%-1.1s%02.2s%-1.1s%08.8s%01.1s' - . '%08.8s%015.15s%01.1s%08.8s%015.15s%015.15s%015.15s%-25.25s' - . '%01.1s%02.2s%01.1s%03.3s%02.2s%010.10s%-1.1s'; - - $data = [ - $bank->code, - $this->lot, - '3', - $this->lots[$this->lot]['registries'], - 'P', - '', - $movement, - $assignor->agency, - '0', - $assignor->account, - $assignor->account_cd, - '', - $title->our_number, - $assignor->wallet->febraban, - '1', // Title's Registration - $title->doc_type, - '2', // Emission identifier - '2', // Distribuition identifier - $title->id, - date('dmY', strtotime($title->due)), - number_format($title->value, 2, '', ''), - '0', // Collection agency - '', // Collection agency Check Digit - $title->kind, - 'A', // Identifies title acceptance by payer - date('dmY', strtotime($title->stamp)), - $title->fine_type, - ($title->fine_date != '' ? date('dmY', strtotime($title->fine_date)) : '0'), - number_format($title->fine_value, 2, '', ''), - $title->discount_type, - ($title->discount_date != '' ? date('dmY', strtotime($title->discount_date)) : '0'), - number_format($title->discount_value, 2, '', ''), - number_format($title->iof, 2, '', ''), - number_format($title->rebate, 2, '', ''), - $title->description, - '3', // Protest code - '0', // Protest deadline - '1', // low/return code - '0', // low/return deadline - $title->specie->febraban, - '0', // Contract number - '1', // Free use: it's defining partial payment isn't allowed - ]; - - $this->register($format, $data); - - /* - * 'Q' Segment - */ - - $format = '%03.3s%04.4s%01.1s%05.5s%-1.1s%-1.1s%02.2s%01.1s%015.15s' - . '%-40.40s%-40.40s%-15.15s%08.8s%-15.15s%-2.2s%01.1s%015.15s' - . '%-40.40s%03.3s%020.20s%-8.8s'; - - $data = [ - $bank->code, - $this->lot, - '0', - $this->lots[$this->lot]['registries'], - 'Q', - '', - $movement, - $payer_person->documentValidate()['type'], - $payer_person->document, - $payer_person->name, - $payer_address->place, - $payer_address->neighborhood, - $payer_address->zipcode, - $payer_address->county->name, - $payer_address->county->state->code, - ($guarantor_person !== null ? $guarantor_person->documentValidate()['type'] : ''), - $guarantor_person->document ?? '', - $guarantor_person->name ?? '', - '0', // Corresponding bank - '0', // "Our number" at corresponding bank - '', - ]; - - $this->register($format, $data); - } - - /** - * Adds a Lot Trailer - */ - protected function addLotTrailer() - { - $format = '%03.3s%04.4s%01.1s%-9.9s%06.6s%06.6s%017.17s%06.6s%017.17s' - . '%06.6s%017.17s%06.6s%017.17s%-8.8s%-117.117s'; - - $data = [ - $this->shipping_file->assignor->bank->code, - $this->lot, - '5', - '', - $this->lots[$this->lot]['lines'], - $this->lots[$this->lot]['titles'], - number_format($this->lots[$this->lot]['total'], 2, '', ''), - '0', // CV - '0', // CV - '0', // CC - '0', // CC - '0', // CD - '0', // CD - '', - '', - ]; - - $this->register($format, $data); - } - - /** - * Adds a File Trailer - */ - protected function addFileTrailer() - { - $format = '%03.3s%04.4s%01.1s%-9.9s%06.6s%06.6s%06.6s%-205.205s'; - - $data = [ - $this->shipping_file->assignor->bank->code, - $this->lot, - '9', - '', - count($this->lots), - $this->registry_count, - '0', - '', - ]; - - $this->register($format, $data); - } -} diff --git a/src/Views/Cnabs/Cnab400.php b/src/Views/Cnabs/Cnab400.php deleted file mode 100644 index 9a6399c..0000000 --- a/src/Views/Cnabs/Cnab400.php +++ /dev/null @@ -1,143 +0,0 @@ -shipping_file->assignor; - $assignor_person = $assignor->person; - $bank = $assignor->bank; - - $format = '%01.1s%01.1s%-7.7s%02.2s%-15.15s%04.4s%02.2s%07.7s%01.1s' - . '%-6.6s%-30.30s%03.3s%-15.15s%06.6s%03.3s%-291.291s%06.6s'; - - $data = [ - '0', - '1', - 'REMESSA', - '01', - 'COBRANCA', - $assignor->agency, - '', - $assignor->account, - $assignor->account_cd, - '', - $assignor_person->name, - $bank->code, - $bank->name, - date('dmy'), - $assignor->edi, - '', - $this->registry_count, - ]; - - $this->register($format, $data); - } - - /** - * Adds a Transaction - * - * @param integer $movement ... - * @param Title $title Holds data about the title and the related payer - */ - protected function addTitle(BankI\Models\Title $title) - { - $assignor = $title->assignor; - $assignor_person = $assignor->person; - $bank = $assignor->bank; - $payer = $title->payer; - $payer_person = $payer->person; - $payer_address = $payer->address; - - $format = '%01.1s%-16.16s%04.4s%02.2s%07.7s%01.1s%02.2s%-4.4s%-25.25s' - . '%07.7s%01.1s%010.10s%06.6s%013.13s%-8.8s%01.1s%02.2s%-10.10s' - . '%06.6s%013.13s%03.3s%04.4s%-1.1s%02.2s%-1.1s%06.6s%04.4s' - . '%013.13s%06.6s%013.13s%013.13s%013.13s%02.2s%014.14s%-40.40s' - . '%-40.40s%-12.12s%08.8s%-15.15s%-2.2s%-40.40s%02.2s%-1.1s%06.6s'; - - $data = [ - '1', - '', - $assignor->agency, - '0', - $assignor->account, - $assignor->account_cd, - '0', // fine percent - '', - $title->id, - $title->our_number, - $title->checkDigitOurNumber(), - '0', // contract - '0', // second discount date - '0', // second discount value - '', - $assignor->wallet->febraban, - $config['service'] ?? '1', - $title->id, - date('dmy', strtotime($title->due)), - number_format($title->value, 2, '', ''), // Specie raw format - '0', // Charging bank - '0', // Charging agency - '', - $title->kind, - 'A', // accept - date('dmy', strtotime($title->stamp)), - $config['instruction_code'] ?? '0', - '0', // one day fine - ($title->discount_date != '' ? date('dmy', strtotime($title->discount_date)) : '0'), - number_format($title->discount_value, 2, '', ''), - number_format($title->iof, 2, '', ''), - number_format($title->rebate, 2, '', ''), - $payer_person->documentValidate()['type'] ?? '', - $payer_person->document, - $payer_person->name, - implode(' ', [$payer_address->place, $payer_address->number, $payer_address->neighborhood]), - $payer_address->detail, - $payer_address->zipcode, - $payer_address->county->name, - $payer_address->county->state->code, - '', // message or guarantor name - '99', // protest deadline - $title->specie->febraban, - $this->registry_count, - ]; - - $this->register($format, $data); - } - - /** - * Adds a Trailer - */ - protected function addFileTrailer() - { - $format = '%01.1s%-393.393s%06.6s'; - - $data = [ - '9', - '', - $this->registry_count, - ]; - - $this->register($format, $data); - } -}