155 lines
4.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Modules\EcommerceFinance\Services;
use Illuminate\Support\Facades\File;
/**
* 账套科目索引(resources/data/coa.json)。
*
* 账套是「小企业会计准则 + 5 位一级科目」,层级用点分:5601.05.04。
* 映射表里写的是「一级科目名 + 二级科目名」两列,二级名往往是第 3、4 层,
* 所以按「一级名 → 一级编码 → 编码前缀」这条路去子树里找,才找得准。
*/
class AccountMappingCoa
{
/** @var array<int,array{code:string,name:string,level:int,category:string,balance:string}>|null */
private ?array $accounts = null;
/** @return array<int,array{code:string,name:string,level:int,category:string,balance:string}> */
public function all(): array
{
if ($this->accounts !== null) {
return $this->accounts;
}
$path = resource_path('data/coa.json');
$payload = File::exists($path) ? json_decode((string) File::get($path), true) : null;
$rows = is_array($payload['accounts'] ?? null) ? $payload['accounts'] : [];
$this->accounts = array_map(static fn (array $row): array => [
'code' => (string) ($row[0] ?? ''),
'name' => (string) ($row[1] ?? ''),
'level' => (int) ($row[2] ?? 1),
'category' => (string) ($row[3] ?? ''),
'balance' => (string) ($row[4] ?? ''),
], $rows);
return $this->accounts;
}
/** @return array<int,array{code:string,name:string}> 一级科目(用于下拉第一级) */
public function level1(): array
{
return array_values(array_map(
static fn (array $a): array => ['code' => $a['code'], 'name' => $a['name']],
array_filter($this->all(), static fn (array $a): bool => $a['level'] === 1 && $a['name'] !== '')
));
}
/** @return array<int,array{code:string,name:string,level:int}> 某编码子树下的非一级科目 */
public function descendants(string $code): array
{
if ($code === '') {
return [];
}
$prefix = $code . '.';
$hits = [];
foreach ($this->all() as $a) {
if ($a['code'] !== $code && str_starts_with($a['code'], $prefix)) {
$hits[] = ['code' => $a['code'], 'name' => $a['name'], 'level' => $a['level']];
}
}
return $hits;
}
/** 一级科目名 → 编码(账套里一级科目名唯一) */
public function level1Code(string $name): ?string
{
$name = trim($name);
if ($name === '') {
return null;
}
foreach ($this->all() as $a) {
if ($a['level'] === 1 && $a['name'] === $name) {
return $a['code'];
}
}
return null;
}
/**
* 在「一级科目」子树下按名称找科目编码。
*
* @return array<int,string> 命中编码;空数组 = 不存在;多于 1 个 = 同名歧义
*/
public function resolve(string $level1Name, string $subName): array
{
$subName = trim($subName);
if ($subName === '') {
return [];
}
$base = $this->level1Code($level1Name);
if ($base === null) {
return [];
}
$hits = [];
foreach ($this->all() as $a) {
if ($a['name'] === $subName && ($a['code'] === $base || str_starts_with($a['code'], $base . '.'))) {
$hits[] = $a['code'];
}
}
sort($hits);
return $hits;
}
/** 全账套里同名的编码(用于解释「为什么找不到」) @return array<int,string> */
public function anywhereByName(string $name): array
{
$name = trim($name);
if ($name === '') {
return [];
}
$hits = [];
foreach ($this->all() as $a) {
if ($a['name'] === $name) {
$hits[] = $a['code'];
}
}
sort($hits);
return $hits;
}
/** 名称或编码 → 编码(映射表里偶尔会直接写编码) */
public function toCode(string $level1Token): ?string
{
$token = trim($level1Token);
if ($token === '') {
return null;
}
if (preg_match('/^\d+(\.\d+)*$/', $token) === 1) {
foreach ($this->all() as $a) {
if ($a['code'] === $token) {
return $token;
}
}
return null;
}
return $this->level1Code($token);
}
}