假设公司审计人员来到您的 “有纸” 办公室,要求提供会计报表中显示的某个交易的支持文档。当文档很少、很容易在纸质记录中找到时,这个要求不是个问题。但是,审计一般都比较深入,并且值得审计的交易通常是比较复杂的,涉及到很多的文档,需要您花时间去跟踪这些文档。随着这一复杂性的增加,越有可能丢失这个或那个证明文件。如果有一个文档不可跟踪,那么整个交易过程就蒙上了一层阴影。
纸质办公室中,文档变得不可跟踪的一个原因在于混乱的索引系统。这个管理人员想要将某个文档存储在这个文件袋里,那个管理人员则想要将这同一个文档存储在另一个文件袋里。为满足两者需求导致的文档重复大大增加了存储需求。对于哪一个是初始的、权威的文档,也会出现争议。
一个完全专业化的会计系统允许关于相关文档的详细信息伴随交易细节信息一起存储在系统中,以便检索起来快捷且容易。在还没有提供这一设施的地方,仍能从技术上提供一种解决方案。本文探究 XML 和 PHP 可以如何提供结构和检索机制,来满足这种审计支持。
与会计交易相关的文档可能遵循以下模式:
- 包含合同决策的董事会决议
- 合同草案和讨论
- 一个或多个这样的文档:
- 管理人员对专有采购的辩解理由
- 从合格供应商取得的多个报价
- 管理人员的报价选择辩解理由
- 与中标方签订的合同,以及附加协议
- 发票,以及每张发票的:
- 已完成工作的确认证明
- 已付支票或其他付款确认和收据
- 已完成工作的最终确认证明
- 与交税相关的文档
在本例中,主管部门作出资金消费决策。管理人员草拟一个满足需求的合同,并通过邀请投标方发起一个招标过程。根据董事会签约规则,选定一家供应商,并签订合同。付款给承包方需要已完成工作和已付款的确认证明,每个承包方都需要在会计账务中反映自己的交易。每个阶段都需要一个实际的文档或权威的替代品,以便管理人员可以将之提交给审计人员。
在小型办公室,用手写的记录本或文件来记录文档的详细信息是可行的。但是在审计上下文中,技术的优势是快速且可伸缩。
您不一定必须使用 XML 作为后端,但是 XML 文档只是一个文本文件,所以它文件小,并且是完全透明和可读的。请考虑 清单 1 中的代码。
清单 1. XML 后端
<?xml version="1.0" encoding="UTF-8"?>
<auditList>
<projects>
<project projid="xyz987">
<boardMinute>2011-04-07-xxxx</boardMinute>
<draftContractA>2011-04-07-xxxx</draftContractA>
<contractA>2011-04-07-xxxx</contractA>
<contractB>2011-04-08-xxxx</contractB>
<contractC>2011-04-09-xxxx</contractC>
<workCertA>2011-04-11-xxxx</workCertA>
<workCertB>2011-04-11-xxxx</workCertB>
<workCertC>2011-04-11-xxxx</workCertC>
</project>
</projects>
<transactions>
<transaction accid="abc123" projid="xyz987">
<soleSourceAuth></soleSourceAuth>
<invoice>2011-04-11-xxxx</invoice>
<cheque>2011-04-21-xxxx</cheque>
</transaction>
<transaction accid="def123" projid="xyz987">
<sourceAuth>2011-04-07-xxxx</sourceAuth>
<invoice>2011-04-11-xxxx</invoice>
<cheque>2011-04-21-xxxx</cheque>
</transaction>
<transaction accid="ghi123" projid="xyz987">
<sourceAuth>2011-04-07-xxxx</sourceAuth>
<invoice>2011-04-11-xxxx</invoice>
<cheque>2011-04-21-xxxx</cheque>
</transaction>
</transactions>
</auditList>
|
清单 1 中的标记描述了一个对会计部门和项目经理都有用的基本系统。auditList
元素是根,具有两个子元素 —projects
和 transactions— 分别是同名部门相关详细信息的容器。初始的实际文档都保存在一个活页夹中,每天有一个单独的活页夹。文档添加到活页夹时,被分配一个惟一的连续号码。根据日期和号码,很容易找到任何单个文档。这个项目的工作涉及到三个独立的承包方 — 一个是专有采购,另外两个是竞争采购。项目经理将所有合同看作同一个记录的一部分,因为他是在项目范围的基础上思考的。会计经理需要一个基于交易的列表,所以他的记录只是指一次付款。accid 属性将是会计系统分配给这个交易的惟一交易 ID 号。这个 ID 提供会计和归档系统之间的正向链接。交易也包含一个与项目经理的列表相关的属性 (projid),并且这也以反向工作,支持项目记录到交易的链接。
注意:合同 A 的专有采购授权书缺失:这一遗漏将被审校人员做上红色标志。这个授权书在这里故意保留为空,作为验证检查的一个陷阱。
假设列表很短,这个文件可以被直观地读取和解释,无需进一步的技术。在比较大的列表中,您需要一些高效检索信息的方法。有很多方式做这件事情,一种方式是利用 PHP 和 SimpleXML 函数库。
从会计角度看,列表的键是会计系统分配给交易的惟一 ID。清单 2 中的代码旨在查询到列表中的所有交易(若没有指定过滤条件的话)或者单个交易以及相关的项目信息。
清单 2. 会计查询
<?php
$transFilter = $argv[1];
$xml = simplexml_load_file("backend.xml");
echo "=====\nSummary for accounts section\n";
if (isset($transFilter)) {
echo "=====\nTransaction filter $transFilter\n=====\n";
} else {
echo "=====\nNo filter - showing all transactions\n=====\n";
}
foreach ($xml->transactions->transaction as $t) {
if (!$transFilter or $t['accid'] == $transFilter) {
if ($t['accid'] == $transFilter) {
$projectFilter = trim($t['projid']);
}
echo "Detail for transaction ".$t['accid']."\n";
foreach ($t->children() as $tc) {
echo $tc->getName()." : ".$tc."\n";
}
}
}
if (isset($projectFilter)) {
echo "=====\nAssociated project $projectFilter\n=====\n";
foreach ($xml->projects->project as $p) {
if ($p['projid'] == trim($projectFilter)) {
echo "Detail for project ".$p['projid']."\n";
foreach ($p->children() as $pc) {
echo $pc->getName()." : ".$pc."\n";
}
}
}
}
echo "=====\nEnd of search\n=====\n";
?>
|
清单 2 中的代码首先在 $argv 数组中查找一个传递进来的参数。可能有一个,也可能没有。然后,清单 1 中的 XML 被加载到一个对象中进行进一步的处理。由于查询是由会计部分提交的,所以它直接转到交易部分,并根据是否给出过滤条件,而循环通过所有交易或者查找匹配所请求标识符的交易。如果找到正确的 accid,相关的 projid 或项目属性就被保存在一个变量中,以便以后引用。如果请求一个特定的交易,就开始第二个循环,检查相关的项目详细信息并输出。
该脚本按下面的方式从命令行被调用,其中 acct.php 是脚本的名称,def123 是从会计系统获得的交易的 ID:
> php acct.php def123 |
清单 3 展示了针对 清单 1 中数据运行该查询时得到的结果。
清单 3. 输出
===== Summary for accounts section ===== Transaction filter def123 ===== Detail for transaction def123 sourceAuth : 2011-04-07-xxxx invoice : 2011-04-11-xxxx cheque : 2011-04-21-xxxx ===== Associated project xyz987 ===== Detail for project xyz987 boardMinute : 2011-04-07-xxxx draftContractA : 2011-04-07-xxxx contractA : 2011-04-07-xxxx contractB : 2011-04-08-xxxx contractC : 2011-04-09-xxxx workCertA : 2011-04-11-xxxx workCertB : 2011-04-11-xxxx workCertC : 2011-04-11-xxxx ===== End of search ===== |
该输出提供检索存储的文档实际备份所需的信息。它返回特定交易的详细要点以及项目上下文。审计人员然后具有对董事会决议、合同、采购授权书、发票、工作确认证明和已付支票的访问权。如果文档都在它们应该在的地方,那么这就完事了 — 大家高兴,审计人员离开,对您的效率和组织能力印象深刻。
一个脚本不仅能够检索文档信息,在文档不完全时,还能给出建议。正如前面指出的,不管是专有采购辩解理由的存在,还是从多个已提交报价中作出适当选择,对于遵循董事会规则都是很重要的。清单 4 中的代码提供这样一种检查。
清单 4. 完整性检查
<?php
$xml = simplexml_load_file("backend.xml");
echo "=====\nIntegrity check\n=====\n";
$i = 0;
foreach ($xml->transactions->transaction as $t) {
if ($t['accid'] == "" or !$t['accid']) {
$accid = "# $i #";
echo "Transaction accid missing at transaction $accid\n";
} else {
$accid = $t['accid'];
}
if (!$t['projid']) echo "Project id missing at transaction $accid\n";
if ($t->soleSourceAuth=="" and $t->sourceAuth=="")
echo "Authorization problem at $accid\n";
$i++;
}
echo "=====\nEnd of search\n=====\n";
?>
|
清单 4 中的脚本首先将 XML 后端加载到一个对象中。它然后查看每个交易,判断是否存在 accid
属性。如果该属性缺失,那么脚本输出一个警告以及已检查的记录数。它然后检查一个相关的项目 ID 号,如果不存在,就输出一个警告。最后,代码验证 soleSourceAuth 或 sourceAuth
元素是否存在并具有一个有效值(在本例中是指非空字符串)。如果该测试失败,脚本就输出一个警告。您可以用其他方式执行这种完整性检查,但是 SimpleXML 提供一种快速且容易的编程式方法。
用以下方式调用该脚本,不带参数,因为代码检查整个列表:
> php integrity.php |
该调用导致 清单 5 中提供的警告,因为您在检查 清单 1 中的数据,其中具有一个已知的文档引用缺失。
清单 5. 来自清单 4 的输出
===== Integrity check ===== Authorization problem at abc123 ===== End of search ===== |
既然您对审计信息具有了基本访问权,更有益的一步是确保所有文档都作为图像可用。然后,当审计人员请求信息时,您可以快速地给审计人员发送一个到在线文档集合的链接,并礼貌地邀请审计人员在需要更多详细信息时联系您,您然后继续去做更为重要的事情 — 保持记录的最新。
清单 6. 带有图像属性的 XML 后端
<?xml version="1.0" encoding="UTF-8"?>
<auditList>
<projects>
<project projid="xyz987">
<boardMinute image="minute987.pdf">2011-04-07-xxxx</boardMinute>
<draftContractA image="contractdraft987.pdf">2011-04-07-xxxx</draftContractA>
<contractA image="contract987A.jpg">2011-04-07-xxxx</contractA>
<contractB image="contract987B.jpg">2011-04-08-xxxx</contractB>
<contractC image="contract987C.jpg">2011-04-09-xxxx</contractC>
<workCertA image="workcert987A.pdf">2011-04-11-xxxx</workCertA>
<workCertB image="workcert987B.pdf">2011-04-11-xxxx</workCertB>
<workCertC image="workcert987C.pdf">2011-04-11-xxxx</workCertC>
</project>
</projects>
<transactions>
<transaction accid="abc123" projid="xyz987">
<soleSourceAuth image="ssauth987A.odt"></soleSourceAuth>
<invoice image="invoice987A.png">2011-04-11-xxxx</invoice>
<cheque image="cheque987A.jpg"></cheque>
</transaction>
<transaction accid="def123" projid="xyz987">
<sourceAuth image="sourceauth987B.odt">2011-04-07-xxxx</sourceAuth>
<invoice image="invoice987B.png">2011-04-11-xxxx</invoice>
<cheque image="cheque987B.jpg"></cheque>
</transaction>
<transaction accid="ghi123" projid="xyz987">
<sourceAuth image="sourceauth987C.odt">2011-04-07-xxxx</sourceAuth>
<invoice image="invoice987C.png">2011-04-11-xxxx</invoice>
<cheque image="cheque987C.jpg"></cheque>
</transaction>
</transactions>
</auditList>
|
清单 6 基本上与 清单 1 相同,只是增加了一些详细信息。一些元素现在具有一个属性 image,它包含一个存储为 PDF、JPG、ODT 或 PNG 文件的图像或文档的名称。注意,在本例中,银行不返回实际支票,但是在支票通过清算系统之后在线提供 JPG
图像。因此,图像是可用的,但是不存储实际文档。
图像的可用性要求会计部门修改查询,以便给审计人员带来便利。清单 7 展示了更新后的查询。
清单 7. 针对图像的 PHP 查询
<?php
$transFilter = $argv[1];
if (!$transFilter) die ('No transaction specified\n');
$path2images = "/path/to/images/";
echo "=====\nAudit response HTML\n=====\n";
$xml = simplexml_load_file("backend2.xml");
echo "=====\nTransaction $transFilter\n=====\n";
foreach ($xml->transactions->transaction as $t) {
if ($t['accid'] == $transFilter) {
$projectFilter = trim($t['projid']);
echo "Detail for accounting transaction '".$t['accid']."'\n";
foreach ($t->children() as $tc) {
echo $tc->getName()." : ".$tc." image "
."<a href='$path2images".$tc['image']."'>".$tc['image']."</a>\n";
}
}
}
foreach ($xml->projects->project as $p) {
if ($p['projid'] == $projectFilter) {
echo "Detail for project ".$p['projid']."\n";
foreach ($p->children() as $pc) {
echo $pc->getName()." : ".$pc." image "
."<a href='$path2images".$pc['image']."'>".$pc['image']."</a>\n";
}
}
}
echo "=====\nEnd of list\n=====\n";
?>
|
清单 7 首先期望搜索针对的是一个特定的交易。如果没有指定会计 ID 作为参数,那么脚本停止,输出一个警告。否则,它将修改后的后端加载到一个 SimpleXML 对象中,并在交易中搜索这个会计 ID。找到时,相关的项目 ID 被存储起来以备后用。脚本然后输出相关文档的信息。现在,输出既包含对实际文档的引用(存在于哪里),也包含一个嵌入在 HTML 标记中的到存储在网络上某个地方的图像的链接。
下面的命令行界面命令:
> php auditresponsehtml.php def123 |
产生 清单 8 中的输出。
清单 8. PHP 查询输出
===== Audit response HTML ===== ===== Transaction def123 ===== Detail for accounting transaction 'def123' sourceAuth : 2011-04-07-xxxx image <a href='/path/to/images/sourceauth987B.odt'>sourceauth987B.odt</a> invoice : 2011-04-11-xxxx image <a href='/path/to/images/invoice987B.png'>invoice987B.png</a> cheque : image <a href='/path/to/images/cheque987B.jpg'>cheque987B.jpg</a> Detail for project xyz987 boardMinute : 2011-04-07-xxxx image <a href='/path/to/images/minute987.pdf'>minute987.pdf</a> draftContractA : 2011-04-07-xxxx image <a href='/path/to/images/contractdraft987.pdf'>contractdraft987.pdf</a> contractA : 2011-04-07-xxxx image <a href='/path/to/images/contract987A.jpg'>contract987A.jpg</a> contractB : 2011-04-08-xxxx image <a href='/path/to/images/contract987B.jpg'>contract987B.jpg</a> contractC : 2011-04-09-xxxx image <a href='/path/to/images/contract987C.jpg'>contract987C.jpg</a> workCertA : 2011-04-11-xxxx image <a href='/path/to/images/workcert987A.pdf'>workcert987A.pdf</a> workCertB : 2011-04-11-xxxx image <a href='/path/to/images/workcert987B.pdf'>workcert987B.pdf</a> workCertC : 2011-04-11-xxxx image <a href='/path/to/images/workcert987C.pdf'>workcert987C.pdf</a> ===== End of list ===== |
为了简单起见,这里做了很多假设。首先,脚本假设所有的文件都在,并命名正确;其次,它假设审计人员理解索引系统。再次,它假设审计人员的浏览器能够处理此文档类型,也许是作为一个下载文件、一个插件,或者是由应用程序打开的文档。
在图像交付给读者之前,您也可以用某些方式对它们进行操纵。比如说,图像需要打上标记,以便打印的时候新文档不会被误认为是最初的归档副本。PHP 提供很多的图像功能,比如说跨文档打印一个大大的 "VOID",或者在角上打印交易 ID。
清单 9 提供一个示例 PHP 片段,用于在文档被发送到浏览器之前,向它添加一个字符串。
清单 9. 向图像添加详细信息
<?php
$im = imagecreatefromjpeg("/path/to/images/contract987C.jpg");
$textcolor = imagecolorallocate($im, 0, 0, 100);
imagestring($im, 5, 55, 55, 'Archive: 2011-04-11-XXXX', $textcolor);
header('Content-type: image/png');
imagepng($im);
imagedestroy($im);
?>
|
在此代码中,从初始文档创建图像,定义覆盖的文本颜色,然后在左上角输出字符串 Archive: 2011-04-11-XXXX
作为图像的一部分,盖住以前在这个位置的任何内容,范围为 55 像素。在理想情况下,文本颜色应该考虑其所在的背景颜色,以便对比分明。然后,最终的图像被交付给浏览器。
其他的 PHP 图像功能包括对图像进行测试,看它有多大,如果大小超过某个推荐的限制,那么将图像缩小到一个对审计人员的屏幕更合理的大小。
假设这里的例子试图允许对一组实际文件进行会计和项目部门索引,这应该很简单,只需在 XML 后端中具有另外两个部分作为根元素的子元素,分别充当会计和项目部门的容器。只要维护好整个文档的格式良好性,PHP 脚本就可以很容易包含或忽略其他部分。
如果一个新任董事会管理人员想要单独保存他的董事会文档,这一方法将需要对 清单 1 和 清单 6 做一些更改。以前者作为一个例子,参见 清单 10。
清单 10. 带有添加部分的 XML 后端
<?xml version="1.0" encoding="UTF-8"?>
<auditList>
<boardMinutes>
<boardMinute boardMin="bm1234">2011-04-07-xxxx</boardMinute>
...
</boardMinutes>
<projects>
<project projid="xyz987" boardMin="bm1234">
<draftContractA>2011-04-07-xxxx</draftContractA>
<contractA>2011-04-07-xxxx</contractA>
...
</project>
</projects>
<transactions>
...
</transactions>
</auditList>
|
在 清单 1 的这个修改版本中,过去在 projects 部分中的 board minute 项被移动到了一个新的 boardMinutes 容器中,其中一个索引 boardMin 被添加到新容器中的一个新的 boardMinute 元素中,同一个索引还被作为属性添加到相关的 project 元素。现在,projects 部分可以找到相关的董事会信息,反之亦然。这样,满足不同管理人员的需求只不过是增加适当的容器元素和索引属性。
本文中探究了一些工具,用于处理审计人员对支持文档的请求。显然,PHP 协同一个被组织为 XML 文件的文档列表,可以帮助确保您在最小延迟下找到并交付所需的文档。这样组织信息有助于,将审计请求提出时压力大、时间紧的工作负担,转变成一个不断完善的文档分类和检索任务。
随着 XML 模式和文档类型的引入(旨在控制条目的添加和输入以及检查缺失的元素),本文中描述的设置会变得更复杂。PHP 和 SimpleXML 函数对 XML 的格式良好性相当敏感,所以利用普通的编辑器手动编辑文件没有利用诸如 Eclipse 之类的专门应用程序那么高效。Eclipse(参见 参考资料) 工作时可以确保编辑后的文档是格式良好的。
完全的无纸办公在业界还比较少见。一个原因是,积极参与的办公室需要运行兼容的软件。如果这种类型系统的所有元素都是开源和免费的,那么完全采用无纸办公的困难就变成了员工培训和管理层排斥态度的转变。
学习
- PHP and SimpleXML:更多地了解这个工具集,它用于将 XML 转换为一个可以用普通属性选择器和数组迭代器处理的对象。
-
PHP 中的 SimpleXML 处理(Elliotte Rusty Harold,developerWorks,2006 年 10 月):发现 SimpleXML 扩展,它与 PHP 版本 5 捆绑发布,支持 PHP 页面以 PHP 友好的语法查询、搜索、修改和重新发布 XML。
- Eclipse 平台入门(Chris Aniszczyk 和 David Gallardo,developerWorks,2007 年 7 月):更多地了解 Eclipse 开发。
- Getting started with Eclipse(选自 DB2 on Campus 书籍系列):查看免费的 eBook。找出 Eclipse 都能做些什么,并使用动手练习进行实践。
- XML 简介(Doug Tidwell,developerWorks,2002 年 8 月):在本教程中了解 XML 基本知识:它是什么、为何开发了它以及更多内容。
- Thinking XML:使用 XBRL 分析财务报告(Uche Ogbuji,developerWorks,2009 年 1 月):了解 XML 在会计中的更多高级用途,学习足够好地解释 Extensible Business Reporting Language 以明白财务归档资料。
- 用 Eclipse 进行 XML 开发 -- 通过 Eclipse 发挥 XML 的威力(Pawel Leszek,developerWorks,2003 年 4 月):查看关于在一个稳定编辑器中编辑 XML 的一些想法,以及通过 Eclipse 利用 XML 的强大功能。
- Scripting in PHP:更多地了解这个广泛使用的、通用的脚本编程语言,它尤其适合于 web 开发,并可被嵌入到 HTML 中。
- 本作者的更多文章(Colin Beckingham,developerWorks,2009 年 3 月至今):阅读关于 XML、语音识别、XHTML、PHP、SMIL 和其他技术的文章。
- XML 新手入门 获得学习 XML 所需的资料。
- developerWorks 中国网站 XML 技术专区:在 XML 专区获取提高您的专业技能所需的资源。参考 XML 技术文档库,获得大量技术文章和技巧、教程、标准以及 IBM 红皮书。
- IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- developerWorks 技术活动 和 网络广播:随时关注技术的最新进展。
- developerWorks 播客:收听针对软件开发人员的有趣访谈和讨论。
- developerWorks 演示中心:观看演示,包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
获得产品和技术
-
IBM 产品评估试用版软件:下载或 IBM SOA 人员沙箱,并尝试使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
- The developerWorks 中文社区:查看开发人员推动的博客、论坛、组和 wikis,并与其他 developerWorks 用户交流。