From 4dbc452b656e882dc0baf8494dc2c4d122f1113f Mon Sep 17 00:00:00 2001 From: HotSwapp <47397945+HotSwapp@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:28:00 -0500 Subject: [PATCH] items --- .dockerignore | 21 +++++++ .env | 2 - Dockerfile | 30 ++++++++++ TODO.md | 52 ++++++++-------- app/__pycache__/main.cpython-313.pyc | Bin 52978 -> 54904 bytes app/main.py | 86 ++++++++++++++++++++------- cookies.txt | 2 +- data-import/FILES_TEST.csv | 2 + data-import/PHONE_TEST.csv | 1 + data-import/ROLODEX_TEST.csv | 2 + delphi.db | Bin 81920 -> 81920 bytes docker-compose.yml | 22 +++++++ 12 files changed, 169 insertions(+), 51 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 data-import/FILES_TEST.csv create mode 100755 data-import/PHONE_TEST.csv create mode 100755 data-import/ROLODEX_TEST.csv create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f0a8e50 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +.git +.gitignore +__pycache__/ +*.py[cod] +*.so +*.egg +*.egg-info/ +.venv/ +env/ +venv/ +build/ +dist/ +node_modules/ +.DS_Store +.env +.env.* +delphi.db +cookies.txt +data-import/* +!data-import/.gitkeep + diff --git a/.env b/.env index 85236cb..78c69be 100644 --- a/.env +++ b/.env @@ -1,3 +1 @@ -# Delphi Database Environment Configuration SECRET_KEY=your-secret-key-here-change-this-in-production -DATABASE_URL=sqlite:///./delphi.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..be396da --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +# Minimal tooling for healthcheck +RUN apt-get update && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application +COPY app ./app +COPY static ./static +COPY delphi-logo.webp ./delphi-logo.webp +COPY old-csv ./old-csv +COPY old-database ./old-database +COPY data-import ./data-import + +ENV DATABASE_URL=sqlite:///./delphi.db + +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD curl -fsS http://localhost:8000/health || exit 1 + +CMD ["uvicorn","app.main:app","--host","0.0.0.0","--port","8000"] + diff --git a/TODO.md b/TODO.md index b39b358..17b3849 100644 --- a/TODO.md +++ b/TODO.md @@ -2,17 +2,17 @@ Refer to `del.plan.md` for context. Check off items as they’re completed. -- [ ] Create project directories and empty files per structure -- [ ] Create requirements.txt with minimal deps -- [ ] Copy delphi-logo.webp into static/logo/ -- [ ] Set up SQLAlchemy Base and engine/session helpers +- [x] Create project directories and empty files per structure +- [x] Create requirements.txt with minimal deps +- [x] Copy delphi-logo.webp into static/logo/ +- [x] Set up SQLAlchemy Base and engine/session helpers - [x] Add User model with username and password_hash -- [ ] Add Client model (rolodex_id and core fields) -- [ ] Add Phone model with FK to Client -- [ ] Add Case model (file_no unique, FK to Client) -- [ ] Add Transaction model with FK to Case -- [ ] Add Document model with FK to Case -- [ ] Add Payment model with FK to Case +- [x] Add Client model (rolodex_id and core fields) +- [x] Add Phone model with FK to Client +- [x] Add Case model (file_no unique, FK to Client) +- [x] Add Transaction model with FK to Case +- [x] Add Document model with FK to Case +- [x] Add Payment model with FK to Case - [x] Create tables and seed default admin user - [x] Create FastAPI app with DB session dependency - [x] Add SessionMiddleware with SECRET_KEY from env @@ -20,20 +20,20 @@ Refer to `del.plan.md` for context. Check off items as they’re completed. - [x] Create base.html with Bootstrap 5 CDN and nav - [x] Implement login form, POST handler, and logout - [x] Create login.html form -- [ ] Implement dashboard route listing cases -- [ ] Add simple search by file_no/name/keyword -- [ ] Create dashboard.html with table and search box -- [ ] Implement case view and edit POST -- [ ] Create case.html with form and tabs -- [ ] Implement admin page with file upload -- [ ] Create admin.html with upload form and results -- [ ] Build CSV import core with dispatch by filename -- [ ] Importer for ROLODEX → Client -- [ ] Importer for PHONE → Phone -- [ ] Importer for FILES → Case -- [ ] Importer for LEDGER → Transaction -- [ ] Importer for QDROS → Document -- [ ] Importer for PAYMENTS → Payment -- [ ] Wire admin POST to run selected importers +- [x] Implement dashboard route listing cases +- [x] Add simple search by file_no/name/keyword +- [x] Create dashboard.html with table and search box +- [x] Implement case view and edit POST +- [x] Create case.html with form and tabs +- [x] Implement admin page with file upload +- [x] Create admin.html with upload form and results +- [x] Build CSV import core with dispatch by filename +- [x] Importer for ROLODEX → Client +- [x] Importer for PHONE → Phone +- [x] Importer for FILES → Case +- [x] Importer for LEDGER → Transaction +- [x] Importer for QDROS → Document +- [x] Importer for PAYMENTS → Payment +- [x] Wire admin POST to run selected importers - [ ] Run app and test login/import/list/case-edit -- [ ] Add minimal Dockerfile and compose for local run +- [x] Add minimal Dockerfile and compose for local run diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc index cf7d9d66cd57e721539f6dbb9d5ef0d80e31a464..68e73162037d820fec3b6443c6b9f17a08ac9799 100644 GIT binary patch delta 9155 zcmcIp33yZ2m3~i8Py6P56IjNJEWjJaECynX7a-We;UPlm6h*dVTOdoyl}td%L}V)@ znWUz-nYih+Axle|Jyl9!(xz-7B(bUU(ZzjjQ<$b(reu^fZPI+-v~$jtWE+Rcw6h5O zzjMw#cRTmId(OS@{jUfgJuTS(U^MDD_^ASK51oF2<9>-u=4VI~f9QL`zF$qxD6{BY znrwcMCe?O+h`y{WV!WTWYwY~a3}3jbnA@q`$v35I6aS#X5r}V`tm$W5MbU3;j1WY`ysM(V;OwA0rQgx41c5BY1no~WnK6YQPsh#5_KB%*Cn-zM_ z#<_IW>5+L99M`>;UMyL}Z%Y`h5A(c%J~MB?DvJZ5k)T&L412<1pVvL$_XWHWw1&+_ z3+Gqqz6j*Q@E3_BHp~}zmnH9jIAvXMsO1ON#sghb=8_+q%ic7Xoi~>s+H?5UM{YfD zU;ab$@~J}WsQ430-g|0p-qH^>9Ml?_GGtHYl)q`H__1Npn}$VCZ1|C(={+--SCPz4 zgiC+V^AFI^mMs#ov#G>=Wn(=5AgwB2t@&pVe1j*4=+1Jh_&6}{Btf<6N z@)$sb2e6S9fU-91ArYV3AB>VGfd3P{x4e?>tz5x>jefJTI=)cNaSwo@>fldO)CxgG zJtqm0(#=bvqykh+8bGb21Jp|fK%-;=G)or1EGZi>N6H1vlkx!zqyoS~sR*!GDgm@g z^8n{drGPf63~+%|4p<>oO7p5Dd#%vH2Zi}uFOHi1T)b2*Ii!X3hO0*M7U42y*3MhW zaj^7@=_CC`VvS1;)bXkUu~veA^!xn<<)Tz4)vr|KaO)Kt$~kTX);fGQ)M!|vSfjv} zq$PAmOQj%4jkLjANe?Z{=2>g%r7n$$lbYyTEr(MQV@k5jr33kL>aA`R)T~wo?OB(_ z*HX*EA|6}G@cXI@1pQ?iC^3IRlJyBm!6ziepO9E*B-&bO#Y%0^;Ns}ThjXcQk-2f@ z%=~JM-~mEJ0WSbpp}UIX(ErgwQf(911Ap*7aP-XsxwPjc9ld2y$z?WZrbic58f$~b zRwLBk4K}N#?<`uM(&1OpuEiZCtkk8bfHKq@;0L%hLMb=EJ66-D7k@u4D>iyR*0u%w z@P6Aiw#%v>;v4YqiRqo5VV^C<$YQ6TM4~Yrd)TlTN1<|VTRb!z_5}CVZ6<@Bpg-n``a?lk(b^C*wR*k87m3)q z{6U|s0c1<&$d-VtagMA}R<`-0du6e;kN9@SvZbge>a(@>A?HswE=6UL$z|a){&+a1 z`OL;1TU*HMlf{ls zA<(PMIWpYmBQZmlZxF@M_eew**OSmlIHv3N4TtKSp{Ot75ag7KTZ)=dhn#?wmX)YM zH`I3Np9Pj!5yt3awe9qsnjH%A49%{)ivJ?Lrfvy8N$;#%s`>_MJV{@!TLprT>)Q11 zf!on(ijfxN(w+-&{#%mk|Iz^6{od~T9ay|HJmnUGJ8Q%i;Rxb znn(3XJ<ZQpkJZ1Vh~EYlI3eh}job-StF>#jaB`s*n3GHA0evdFrHfOZ&Qbt< zI;XV~m{(RH^Gb7v9hf)DL}1>iDuf#2oUUu7dR$lDwNyP`qXzl-QV}I91*BK&x^?Oa zl^QfBj5=qwFi}wNGz#K5qv*^|Txg7mc~9f&oJM#B;S9oSG_-P6#p_7UtnqWmjv62J9%O%k(21}g;R3<}go_B12>*%z z+bu^vhk)q>c^hF1!mkj14d5^maIR+XE|lDh@Ee5RBHW7b4#K}7e1I^F@F#>2!X?VP zO6bowX!62Cp`g!=X#)8OO0Gxm+)n@EE=sIj^;<=J$EQU#5n_)@QUhxKe`1?dhoKE3 zTPli4i=@R;jZ|A}xI&zRXeBkUIJX3&)0{Zh$l}~mh;xlB&NWH!FCFKWNz2o5Zt1Kz zw_-XDtW3wb=IJ=cT2rrLac(t>b4+4PN!GA9cNKkltz9rmtd?Ma-u|-oFhn_sw`si= zmjUA8S~}j^!8cKia3a%AMZs1kQO%O1bTl&(eN!*G3j47b+Dxn38jAE%b0#`9!E)kH zTDMXiG|~5Z3h4eelfG@%+zz!Bh$(A?23tilM0hqvZE7yBQ%@!&>|K2xd(d}J5A0~kd5vBNFU6@ z#|{&*!W0`%^RqN#rZ(d9`lFdS#ELHGXR8{whN2K{+lD-35C&d&89Q6i&^ipqhNx}x zO=#hQX)X@zEIrw;XxriN;ey&Y)XwX#X)y^=#C9(Ze+O)hDq*3I1At; zuOT@n&`igekMo*s-TnxAnr*;O1M4fRzK{Cn5Z(ijRi6H+f49$}mem9JCIv%mpQpd; zte+c|3a%8D2p+-y>!?(Qr56zjaY8IAu}H;&6bnswq`G2!l30MUV1Sw(pW?{Q2~xj9 z*-WHjef=I~GcoH%WG~V0ZD`QXgP}YGe-VR{Wl!@ld&(MJlN5l$?5R2_0)^RA?r3jP zjWlFWNNbUXW2_WD(MkIl+0fGFgHQq)xe`?&LhAA$7{1ENDoUA_KV-wpE5SURXtj3d3nv@3z?@t&59m`REnUUR)5Qv)Pg^0eDpsE1kvSu*GXZlp zO919!h7_X zbl2bJ*&kvX_aIy;-zvj`iwHBGjpu(-f$-^`-9D3g&Gzg$PCdt;nFBAxIYh7r%)IJ! zi-A`tz?C0P{AK45pLlfJx76Kpt%T~#NNn3k0_&W+V|0 zvG(;ye)<97f8wQkvDwu5;H{nc;%YF!t%*PHJgner5|0f3l3$M2cc=-TW!aH|;J!@I zqY17N!5Zw81D?lXz#0@hNnZ+eitB*<9#w_kfn@Q$aIV-1{4Wx^8>76Uh^PBV%cde! zz8aw$zzH2)z>aJHve=LLLFzoT4W+2UPDT!abtUUY;9k_7QoBTTky5b-RDP4Fjo1|8 zwLtzyLK?ZGFiJo=6l6VXgm&+#Rb7WoZKJpCsam!L$rdbH3*gYp%7G9WP95B~Vwnp8 z4;ZQaq64Yx=+E~QSFriw8H%v>J_D3&^bryw?qRqq@eKN)kCMIRsvS^b5EAWsuU6!4 z%jnDMk>IXi=%yg)rH|fxHGdtwbaSED2gMCkcT1SxLT|n$!uKcs{gy$Ie~n&y`*j6a z7wiHzyb&^p?F=FK>8rPI<}c8KJLaz`K@FwX7wMOE{lo{Euh;E~rnbN$969cTvgi-^ zclpShC^(Ps0|X}}cdUrd%o&~5VNRKCR&UrPn>m}w1yGW;*qj>@=V3C8#X*Fd5CQku|=cmr*1HMG-Spx6S@h;}MNT>-9v2`a&M!CHp|DCDD54W{oQCS^xBp z0e%}5?mK88*timA=_NmFGDm-zw0Muh3geA<71uJ{$u`h4P+ z`|sAOTCl=BR6k}@F*9}3hOrf@J5bt9Lt`Z>=K1Y(bS!rRYo#&M17Rf?1Y~VLT*n1` zu!gc~0ItG(-qbw>ZaKfx0XS4-2p*{_&(hzIb>!TQ?c4>ztx(83ej9E7(zWH&79i-b zvU<=L4frEbazE;~VIzdT{H6Q~w#EDkd3<}*EhGCiWSN1#PCxq63jS@{@KDZY(YhHQ z`6_Bmdxosv?Fsn3?kH@m5!NX=gbIfdo?xm7GnUO*JrEj9-Emx^k33Ybe-X5PB48dU zQ0L243B{Lpt6Lw(s?3^qA$0?QLz%J#v*rO1$eQ7h*AHJKys34%2bIK(O^!X<(Ru;r zWkp)Mc9L|7h%RcPXjBf(ProtqgY62%VW(Q)0TA@QjJfWfP5RZ#t@Dou(e`d zq-AM*#2@pe#&(Rp{MZulI4E@`{`eTL;O&VIzqV6pVZOt13by&3pf^6bp=w5W0$W8_ zfy*cd&Vy}E*+^ZWzj(4`<3%Jthrp70_EzBM4p|6?!en~nguN-aHNXzcoW_nVv*uH? zp@UB~h_8ciU*aoI-O1DYkL4?VprSW^ZY%Gi?%jEG?HBY}${|>%-TVWBQ#5cvrAt8{ zsmRUlMNefv3>f&MS;1VxjCc!NRV>R<^UWJLu8F6-vz(qgY!J&it~Z-5rdEX{((s9b z0+ow5ag7Q|Aqgwt2EC38s_MC*n)W=GPdD$^6$UjfO&{FTy5N3l6?EkRL%391is+5` zIXT))=kQ@mxqZK+wsX|^Tw$)H%M=8)E^XgJsG_C3&q}}goL01CINfGmQo?nTE>(*@ zm#@>o$21+&hfj60^ck1Y*JpY$O!oqx-1^#~U46QinQm&Gr(BR0SS4fTHV3*hpmS~( zS=T_P25g>g%HT4vrVMkNVuISsnljI7iqS3=#x(qSN;q!&4Yd9{dGw{{Eje?{JfCx^ z*(Z;+U(nExpVwEu)mxdW2em%yGBv@PH5K>N~kXdaFe3NnBUdHv>GPTv5Cl9;L zEOCP=V;CD(%B|;}j?A}}Ikj?U*Q&G3uH}4xR-O7;J;4hSgxj($jh~!Y6As**3lm$O zso*lxx4yZi^fF6E6s4S`#EUif_`{yGIdL!Vq^~3v>exODDM|rP|Cp$^6odB?%ryzd zbMg>vesQtF%G0427xSm+1267(TIas+EDgDURmu=9B3$9cPvtfDVCjAYCBm2K4c{t= zvrV6E1lNF2R_^lcjgYh0|J$haYlN8;EFDMLJ~6Z5u~EE+b#Pab^&?@pYffJ{v$!^A zW>!#z@5lJXSWOOL4=kD%>M_T0r)rb)pmY)j8nIyna2T_T9<&~`()mBum+ot)hSOW= z;Uh+iDf{!=@7_K-blzCHuX9Rmq(_caShDgS)Ev}|m7KR!?%Oz}w$Ps)DK};3KUjUR zdTh=4Y{$M$Q}nM#7JpK&=G*H`P`U76;n?EyS@wNbPtnk~-_gClUcnh`AAQit)9a7U z=X2@JNB6|hnzuK1SM%TDuf9t8-3}d)XEfEDcC5cxC(W~IUeY79_8aoU3aB0ZfZ4j+k}Z?9+-&| z(F7$EHW8Uh&`Rt+_N1U1$2FLsRo}Jq=cx0$-SJZ(a5{45y3GZ&c^Se*ge!RrTQ^*& zl(RgK)QqoO!8g8?T6ZdsmY-_s!}&O}|5wxb)Jc#Z(tkNsCQd;&&cuaNZoUXp0Z0Hs zgM)BUm-_7U42a0V#P(NQJbyKP_RM|!R=WDtb#Zpx#;(jBMXNrJa2VkUgik+9v9zlb z_3*ev@I*uKEF&x7GCE4|C_(U$z%pZkDJQ`ci5v%zm3^U5fGo#A&0ZU3O@fmk9SF_X z?g{`|vl_Cak$~?i@;(&6e=f2svGnYIzHu3!eW>D4An@>D=M}1ZIOxrvLx| delta 7476 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 diff --git a/app/main.py b/app/main.py index 570491e..8e6a9b6 100644 --- a/app/main.py +++ b/app/main.py @@ -196,10 +196,15 @@ def validate_csv_headers(headers: List[str], expected_fields: Dict[str, str]) -> if not matched: result['errors'].append(f"Unknown header: '{csv_header}'") - # Check for required fields + # Check for required fields (case-insensitive) required_fields = ['id'] # Most imports need some form of ID for required in required_fields: - if required not in result['field_mapping']: + found = False + for mapped_field in result['field_mapping']: + if mapped_field.lower() == required.lower(): + found = True + break + if not found: result['missing_fields'].append(required) if result['missing_fields'] or result['errors']: @@ -263,15 +268,26 @@ def import_rolodex_data(db: Session, file_path: str) -> Dict[str, Any]: } expected_fields = { - 'rolodex_id': 'Client ID', - 'first_name': 'First Name', - 'middle_initial': 'Middle Initial', - 'last_name': 'Last Name', - 'company': 'Company/Organization', - 'address': 'Address Line 1', - 'city': 'City', - 'state': 'State', - 'zip_code': 'ZIP Code' + 'Id': 'Client ID', + 'Prefix': 'Name Prefix', + 'First': 'First Name', + 'Middle': 'Middle Initial', + 'Last': 'Last Name', + 'Suffix': 'Name Suffix', + 'Title': 'Company/Organization', + 'A1': 'Address Line 1', + 'A2': 'Address Line 2', + 'A3': 'Address Line 3', + 'City': 'City', + 'Abrev': 'State Abbreviation', + 'St': 'State', + 'Zip': 'ZIP Code', + 'Email': 'Email Address', + 'DOB': 'Date of Birth', + 'SS#': 'Social Security Number', + 'Legal_Status': 'Legal Status', + 'Group': 'Group', + 'Memo': 'Memo/Notes' } try: @@ -404,12 +420,35 @@ def import_files_data(db: Session, file_path: str) -> Dict[str, Any]: } expected_fields = { - 'file_no': 'File Number', - 'status': 'Status', - 'case_type': 'File Type', - 'description': 'Regarding', - 'open_date': 'Opened Date', - 'close_date': 'Closed Date' + 'File_No': 'File Number', + 'Status': 'Status', + 'File_Type': 'File Type', + 'Regarding': 'Regarding', + 'Opened': 'Opened Date', + 'Closed': 'Closed Date', + 'Id': 'Client ID', + 'Empl_Num': 'Employee Number', + 'Rate_Per_Hour': 'Rate Per Hour', + 'Footer_Code': 'Footer Code', + 'Opposing': 'Opposing Party', + 'Hours': 'Hours', + 'Hours_P': 'Hours (Previous)', + 'Trust_Bal': 'Trust Balance', + 'Trust_Bal_P': 'Trust Balance (Previous)', + 'Hourly_Fees': 'Hourly Fees', + 'Hourly_Fees_P': 'Hourly Fees (Previous)', + 'Flat_Fees': 'Flat Fees', + 'Flat_Fees_P': 'Flat Fees (Previous)', + 'Disbursements': 'Disbursements', + 'Disbursements_P': 'Disbursements (Previous)', + 'Credit_Bal': 'Credit Balance', + 'Credit_Bal_P': 'Credit Balance (Previous)', + 'Total_Charges': 'Total Charges', + 'Total_Charges_P': 'Total Charges (Previous)', + 'Amount_Owing': 'Amount Owing', + 'Amount_Owing_P': 'Amount Owing (Previous)', + 'Transferable': 'Transferable', + 'Memo': 'Memo' } try: @@ -1180,11 +1219,6 @@ async def case_detail( async def case_update( request: Request, case_id: int, - status: str = None, - case_type: str = None, - description: str = None, - open_date: str = None, - close_date: str = None, db: Session = Depends(get_db), ) -> RedirectResponse: """ @@ -1197,6 +1231,9 @@ async def case_update( if not user: return RedirectResponse(url="/login", status_code=302) + # Get form data + form = await request.form() + # Fetch the case case_obj = db.query(Case).filter(Case.id == case_id).first() if not case_obj: @@ -1208,6 +1245,7 @@ async def case_update( update_data = {} # Status validation + status = form.get("status") if status is not None: if status not in ["active", "closed"]: errors.append("Status must be 'active' or 'closed'") @@ -1215,13 +1253,16 @@ async def case_update( update_data["status"] = status # Case type and description (optional) + case_type = form.get("case_type") if case_type is not None: update_data["case_type"] = case_type.strip() if case_type.strip() else None + description = form.get("description") if description is not None: update_data["description"] = description.strip() if description.strip() else None # Date validation and parsing + open_date = form.get("open_date") if open_date is not None: if open_date.strip(): try: @@ -1231,6 +1272,7 @@ async def case_update( else: update_data["open_date"] = None + close_date = form.get("close_date") if close_date is not None: if close_date.strip(): try: diff --git a/cookies.txt b/cookies.txt index 1aa9e0f..f5452af 100644 --- a/cookies.txt +++ b/cookies.txt @@ -2,4 +2,4 @@ # https://curl.se/docs/http-cookies.html # This file was generated by libcurl! Edit at your own risk. -#HttpOnly_localhost FALSE / FALSE 1761003936 session eyJ1c2VyX2lkIjogMSwgInVzZXIiOiB7ImlkIjogMSwgInVzZXJuYW1lIjogImFkbWluIn19.aORUoA.KSAst9pcXJJJHTc1m-mY_jQ0P6A +#HttpOnly_localhost FALSE / FALSE 1761009191 session eyJ1c2VyX2lkIjogMSwgInVzZXIiOiB7ImlkIjogMSwgInVzZXJuYW1lIjogImFkbWluIn19.aORpJw.oMEiA8ZMjrlLoJlYpDsM_T5EMpk diff --git a/data-import/FILES_TEST.csv b/data-import/FILES_TEST.csv new file mode 100755 index 0000000..3c8524e --- /dev/null +++ b/data-import/FILES_TEST.csv @@ -0,0 +1,2 @@ +File_No,Id,File_Type,Regarding,Opened,Closed,Empl_Num,Rate_Per_Hour,Status,Footer_Code,Opposing,Hours,Hours_P,Trust_Bal,Trust_Bal_P,Hourly_Fees,Hourly_Fees_P,Flat_Fees,Flat_Fees_P,Disbursements,Disbursements_P,Credit_Bal,Credit_Bal_P,Total_Charges,Total_Charges_P,Amount_Owing,Amount_Owing_P,Transferable,Memo +TEST-001,1,Family Law,Divorce case,2024-01-15,2024-06-15,EMP001,150.00,active,FC001,,10.5,0,1000.00,0,1575.00,0,0,0,0,0,0,0,0,0,0,0,0,Test case for development diff --git a/data-import/PHONE_TEST.csv b/data-import/PHONE_TEST.csv new file mode 100755 index 0000000..c08a4be --- /dev/null +++ b/data-import/PHONE_TEST.csv @@ -0,0 +1 @@ +Id,Phone,Location \ No newline at end of file diff --git a/data-import/ROLODEX_TEST.csv b/data-import/ROLODEX_TEST.csv new file mode 100755 index 0000000..44c70bd --- /dev/null +++ b/data-import/ROLODEX_TEST.csv @@ -0,0 +1,2 @@ +Id,Prefix,First,Middle,Last,Suffix,Title,A1,A2,A3,City,Abrev,St,Zip,Email,DOB,SS#,Legal_Status,Group,Memo +1,,John,,Doe,,Attorney,123 Main St,,Apt 2B,New York,,NY,10001,john.doe@example.com,1970-01-01,123-45-6789,Active,Group A,Test client record diff --git a/delphi.db b/delphi.db index 11b5fc38ef2c9a735c048e7da79906720c37d81a..8330bff42c387fefb9103dcaa11f79ef89d5624e 100644 GIT binary patch literal 81920 zcmeI*&u`o283%CEvL(y;q5Nnd1ep;oPL?RG9g>o4N2kfQoH(zE5g zDkPP}D~7>K((N$xk66#W_1x>OI|djqV8F1$PCInlp*ie*Nl_AI(M3VcB)n6|&q3j_0|b3j)V+SLyFZ^f!2%rVE3?3;LJuT8>)0 z%1wNkIYy(Wd6`Rb$G<+V9R2H2>*(0Xe@4DZULF3+@E;SG<9{C77&;aEE@tvy(Q1eM z@ypZk^tCbmv2JSQnf^@C>kZ3x6~lVyD7x15>Z938saP!u3(Iq*yTXo|LU~!}sx4g6 zwaoJerxNM0G5+_wtF9ZQE8Mksy47yAII~y^H!+gbHM+fO=|-s{ESIao^6KK^Cju?@ zJLzsUi12B#GJCUFxsn$%p*kD7K@?N1cWTJ_e4i=})!poBq^A|fRo$l3j>}?(ZQr$A z)lh8fsS|XMl{RdvMjVG|J0ecA#v)41YMO5MD#W%eJE~YCuBsc3P%Yi9vi50TY?ro9 zNA?`eM0H`QB+QlOi>r%O;mYi4rBYh1Dy(#+T3ovAb+l&H8wTlFiM3U;iP}^AV{P58 zEG!i(Yr-!|YhHFoGRr69>C7lkg0a(3x9Y@n*}w@FPxX$PAVBM|ArrjxTKwIe6N&Wb zDF34Ajh7%6{5=^ODM6`lC32Y5REMzb^czC0)fJ60v^l*j`a7Y723ITZYTZlV{9?K2 zWke%R&DI;PZkg>Y)iz1(kzzLM>%{IIH+!JYx;S61looC*vrJrR?PEr$l;%rx1k9FJ z1Xkv>a#Tsh(;uJb^&m%@Rci)GYA>JY%~CtkN>lr_pQq1`C(`H7^DmEhd1?pS3#m}1 z+Qsjfs$l(aO6c*N>txVm2+?@e8Iu$8^;B0quaM-%Bwm-v?nFM-|h?&V{lG~!u9zU#qf zlMP}Mv(}mPTf^i4k~niDo*ql{Ye5pdqmpLC`+KxEZ9$Yxik;*4?npd6p60&@x8aX@ z%d2*z)zIql!kyKwv$Nk`t7ccDt&+@hE}2ND)BH=t8v-~;ft3k}2W2t6Xw2i$ul$#CKO6a7^rjXu~Au9__ z%fWt!CuXlSn40H@IW^T&xq`s`W$h2)<$ zy@+3VIFwAkY?o~Ao-J?naAI2LML*L2oJXFy#B|shG8iM0KXdQLA^8UCx8m{i<#W6m zsQdfO*jr19UiVH7?>Oqp;je}g>8W%4cB;2A+cGSAJ>6+E-fDEmVd}R=uNZ?BLfhyR zXP)zeiS)U1{L3}}fb~~w^=&AW=T_k^iSD%9&PpqlhU&QC%jWip){cnObxot!+PbN` zx@xo=4AM+5K~;0BtJqJ!s%bX8CT^Frt8;b~)pWO0xwUOJPjseq*{k>q_Xml$g7-8K8X#WLF2f^eubmv4htRa%P#x!~Woyc&>U7>c z?RbNwI|{pMc|wGla(S^-T=o*Yj|pP$&H2=yIQoMJ1Rwwb2tWV=5P$##AOHafKmY;< zCUBo0=F;0k$t1?@pt7(KZaHfNs5*Qc7YDCfr|aa^1fVY{|{X1 zhr_go=nDiO009U<00Izz00bZa0SG_<0(&69KL0-$OC=?I{(lcN(F6n_009U<00Izz z00bZa0SG|g5DRoZ|Nn|hy*k7lMb{tz0SG_<0uX=z1Rwwb2tWV=5a=uLGd{s3&x~@} zO{-4wdHS{gayHlf`G5N1f2ojfeg8kc|F1713LyXi2tWV=5P$##AOHafKmY^l<=GD4xYzd3%)76HixV#Ct<=dO`JHd)3rl(~wOMgQ>W|2Qk z4yBmHdD@5MSC8M4qGr__263szg_}gxh%G!(4V}{ImMQp71>xTP3#;ZM(|T$On_i`Y zaCt$yeE-5HLZ8Sy#Z{_X4H8wMLLRENrkf9=DwG?8; z^U`##6Y(cf7TM0kysmh2*bx=o%$1xs~$9*qjlm1m+tD=TAB zp&Pc0Q93u0%|@rMU1n#fLFpPuSmY#3JneeLD^$^QfC+b%l* delta 242 zcmZo@U~On%ogmF9IZ?)$QF3F#5_>iV0R{&C^P2?~j`GXtGx9QsONugpfiZ)jk+F%X zb53SzUP(x5amnPj_2Lla-!}^$c*`%$!7R=RR>K9-05azfP~tuR=AZf83Y%FD{E^>u zfh7RQU{PS=UjejY9)D68BOimPtfD+42O}ecF@tdl&|nbOOD-54GnY+%oPj_tV|89OpLJzp*eva=pbfhPEG*wuRVqU diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3b57e71 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: "3.9" + +services: + web: + build: . + container_name: delphicg-web + ports: + - "8000:8000" + environment: + - SECRET_KEY=${SECRET_KEY} + - DATABASE_URL=sqlite:///./delphi.db + volumes: + - ./data-import:/app/data-import + - ./delphi.db:/app/delphi.db + - ./old-csv:/app/old-csv:ro + - ./static/logo:/app/static/logo + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:8000/health || exit 1"] + interval: 30s + timeout: 3s + retries: 3 +