From 6fe34784a66568356e17d901c34713425d44938a Mon Sep 17 00:00:00 2001 From: SKYJAMES777 <3886190@qq.com> Date: Tue, 23 Jun 2026 14:22:21 +0800 Subject: [PATCH 1/2] Implement invoice intake and three-way matching logic with unit tests --- src/invoice_processing.py | 108 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/invoice_processing.py diff --git a/src/invoice_processing.py b/src/invoice_processing.py new file mode 100644 index 0000000..e339a55 --- /dev/null +++ b/src/invoice_processing.py @@ -0,0 +1,108 @@ +import csv +import json +from datetime import datetime +from typing import List, Dict, Optional + +class Invoice: + def __init__(self, invoice_id: str, vendor: str, amount: float, date: str, po_number: Optional[str] = None): + self.invoice_id = invoice_id + self.vendor = vendor + self.amount = amount + self.date = datetime.strptime(date, '%Y-%m-%d') + self.po_number = po_number + +class PurchaseOrder: + def __init__(self, po_number: str, vendor: str, amount: float, items: List[Dict]): + self.po_number = po_number + self.vendor = vendor + self.amount = amount + self.items = items + +class Receipt: + def __init__(self, receipt_id: str, po_number: str, items_received: List[Dict]): + self.receipt_id = receipt_id + self.po_number = po_number + self.items_received = items_received + +def load_invoices(file_path: str) -> List[Invoice]: + invoices = [] + with open(file_path, 'r') as f: + reader = csv.DictReader(f) + for row in reader: + invoice = Invoice( + invoice_id=row['invoice_id'], + vendor=row['vendor'], + amount=float(row['amount']), + date=row['date'], + po_number=row.get('po_number') + ) + invoices.append(invoice) + return invoices + +def load_purchase_orders(file_path: str) -> List[PurchaseOrder]: + with open(file_path, 'r') as f: + data = json.load(f) + return [PurchaseOrder(**po) for po in data] + +def load_receipts(file_path: str) -> List[Receipt]: + with open(file_path, 'r') as f: + data = json.load(f) + return [Receipt(**rec) for rec in data] + +def three_way_match(invoice: Invoice, po: PurchaseOrder, receipt: Receipt) -> Dict: + """ + Perform three-way matching between invoice, purchase order, and receipt. + Returns a dictionary with match status and details. + """ + result = { + 'invoice_id': invoice.invoice_id, + 'po_number': po.po_number if po else None, + 'receipt_id': receipt.receipt_id if receipt else None, + 'vendor_match': False, + 'amount_match': False, + 'items_match': False, + 'overall_match': False + } + + # Vendor match + if invoice.vendor == po.vendor: + result['vendor_match'] = True + + # Amount match (allow small tolerance) + if abs(invoice.amount - po.amount) <= 0.01: + result['amount_match'] = True + + # Items match (check if received items match invoice items) + if receipt and po: + po_items = {item['sku']: item['quantity'] for item in po.items} + rec_items = {item['sku']: item['quantity'] for item in receipt.items_received} + if po_items == rec_items: + result['items_match'] = True + + # Overall match + if result['vendor_match'] and result['amount_match'] and result['items_match']: + result['overall_match'] = True + + return result + +def process_invoices(invoice_file: str, po_file: str, receipt_file: str) -> List[Dict]: + invoices = load_invoices(invoice_file) + pos = load_purchase_orders(po_file) + receipts = load_receipts(receipt_file) + + results = [] + for invoice in invoices: + # Find matching PO + po = next((p for p in pos if p.po_number == invoice.po_number), None) + # Find matching receipt + receipt = next((r for r in receipts if r.po_number == invoice.po_number), None) + match_result = three_way_match(invoice, po, receipt) + results.append(match_result) + + return results + +if __name__ == '__main__': + # Example usage + results = process_invoices('invoices.csv', 'purchase_orders.json', 'receipts.json') + for r in results: + print(r) From 5fc08785840b5942556bd1db6002ce9330068c7e Mon Sep 17 00:00:00 2001 From: SKYJAMES777 <3886190@qq.com> Date: Tue, 23 Jun 2026 14:22:23 +0800 Subject: [PATCH 2/2] Implement invoice intake and three-way matching logic with unit tests --- tests/test_invoice_processing.py | 78 ++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/test_invoice_processing.py diff --git a/tests/test_invoice_processing.py b/tests/test_invoice_processing.py new file mode 100644 index 0000000..3c5a1c8 --- /dev/null +++ b/tests/test_invoice_processing.py @@ -0,0 +1,78 @@ +import unittest +from src.invoice_processing import Invoice, PurchaseOrder, Receipt, three_way_match + +class TestThreeWayMatch(unittest.TestCase): + + def setUp(self): + self.invoice = Invoice( + invoice_id='INV-001', + vendor='Acme Corp', + amount=1000.00, + date='2023-01-15', + po_number='PO-123' + ) + self.po = PurchaseOrder( + po_number='PO-123', + vendor='Acme Corp', + amount=1000.00, + items=[{'sku': 'A1', 'quantity': 10}, {'sku': 'B2', 'quantity': 5}] + ) + self.receipt = Receipt( + receipt_id='RCP-001', + po_number='PO-123', + items_received=[{'sku': 'A1', 'quantity': 10}, {'sku': 'B2', 'quantity': 5}] + ) + + def test_full_match(self): + result = three_way_match(self.invoice, self.po, self.receipt) + self.assertTrue(result['overall_match']) + self.assertTrue(result['vendor_match']) + self.assertTrue(result['amount_match']) + self.assertTrue(result['items_match']) + + def test_vendor_mismatch(self): + invoice = Invoice( + invoice_id='INV-002', + vendor='Other Corp', + amount=1000.00, + date='2023-01-15', + po_number='PO-123' + ) + result = three_way_match(invoice, self.po, self.receipt) + self.assertFalse(result['vendor_match']) + self.assertFalse(result['overall_match']) + + def test_amount_mismatch(self): + invoice = Invoice( + invoice_id='INV-003', + vendor='Acme Corp', + amount=999.99, + date='2023-01-15', + po_number='PO-123' + ) + result = three_way_match(invoice, self.po, self.receipt) + self.assertFalse(result['amount_match']) + self.assertFalse(result['overall_match']) + + def test_items_mismatch(self): + receipt = Receipt( + receipt_id='RCP-002', + po_number='PO-123', + items_received=[{'sku': 'A1', 'quantity': 9}, {'sku': 'B2', 'quantity': 5}] + ) + result = three_way_match(self.invoice, self.po, receipt) + self.assertFalse(result['items_match']) + self.assertFalse(result['overall_match']) + + def test_no_po(self): + result = three_way_match(self.invoice, None, self.receipt) + self.assertFalse(result['overall_match']) + self.assertIsNone(result['po_number']) + + def test_no_receipt(self): + result = three_way_match(self.invoice, self.po, None) + self.assertFalse(result['overall_match']) + self.assertIsNone(result['receipt_id']) + +if __name__ == '__main__': + unittest.main()