¦ÎÑpºrG|œ@‡í!ásÞ„Ûœõe¥¡)×¬®¸–¶Æ™äàü`´%äÔÈìÆõq7¢b‚/pŒŽ*H–ÍNµº”bG/Ë¹UÝõ(D8¯™_�K+Ú Œ/VO³�+¨¢;¤´ÙU­ç–¨Óæd ‘Â“ÙÆIqG�nu$áZ¹@"2XÓ 4u½¡>H~š�á—L·FtˆŸXÇÖU%�qÐ¦–UF¯ó‡uø"�ØGåÑüñýÇ‡Õ§á+ÙŽ/_-&‹‡:$ÒpŽñ¢ÚÉ{5Ç•³g_¥Áóøæ³œ3ÿv†ÆëœÎÐòŽ'^o;Š�–‹§�g¡Úi	§ÇÉŒtðãäÓŽE?^½æÖ0TÐëçº…Êpf>Ñ„ÆìØ{›Î”®†ŸÈd«ß	hÊŒ¢ÛÒ‘Ó…^†Éšn1j­œÜJù8í”ÔSA¸ô@i^þÔHKú®‚¶’6;¸cG=*‹Ù=|@D¯i ÛÙÐ9‘�[‘ä·ŽTuÓd;¸¬f[2f‹“^«öŽ÷yGRwN?X£U+]R‘Ç¨õ@Ê4#”Ë‰)þìèZêûŸÙÛyC+×!yŒç-®E‹™½n_Jß2�ø�€Þ	¬Ç¤Ðé3„H£«Ç:gn2íÁÅy´¸”þ-áÎÕçÉoÐ£ŸOŸÖÚž·¹)»Vèô	zÔË¼á=»î ŠíQ’WCÚeÃD,lñCÄ»P�>_pÂñ¡�‚P@½ÆßßØÄ“ÙJûìY‡ˆÕÑbx„¹ÈñÃ ûê‡ó&F‰”¿µÏÿ�X¾�D˜œjÂîz¿ÄNæ¢M´™{>iPš.n_Jß2�ø�€Þ	¬Ç¤Ðá�ž‡œóÓO#"¨Âi´,lvËÇh»Š"rRh¬41K8ûþ
yÖÒü;µV=à¤ö2üÎV)ïvu õžÌoVÎÞ¹¡KÒ¿ªd›�éå%�ß¢ðÞµÅ)Ê… ›Ê0]¢ÿù2í°¬ÐVWeÐ4cÀØÏÚ¸æf
'cŠq"ƒ~�œµî|^� aÑX€ië(nÁ„®“üµÅÑŽ¨ sYó(`•¤€JåWëÎ.BÑÒ`W×BÎ#.qN¥w©ÌO9ú–¿Æâ0›ë'ó¹ay(ÙÝ~*Æ¡S,ó¼¢¶ÓZ…[ò"M:_�ñi•N?ŒšW=û[p1ø<2£óMÜ*8d8À˜%žJWžÆŸÐ™Æ2ÊÚ€?‡’�-Ð/Aþˆƒ‡A÷&¤mþo¼T›£ªøHù/„â ]'a_pÈ°üWÖ»j	°¸kræ2xÖ`ÕÄJýA& fñ þ‚+"ï<1Ùš˜×ùŒ$¡xÄl;á”p?ó"*€×œ²lš“ù¡›jÖ1OaÌûßéŒfN'Gò8²ìÓg©ÌÙuÆ>ŽIÛLç`JØYz ¸›`³GÐâ0&†+]Q)‚}–IêùOÙÑ‚É�¦GP'[Aúy— …1Àø›«††…iÙ%p”1E3œ9&Ïlî³{ãâšL©þN�£àõÄl“ŠÌ±JÓÇÔÊ	F]Ð>DÑ½fì ›HÝdS|ÿ:Ò›ìÀ¸L Aü}è€®â£±]#,vDMÍÛvÁÐôÂehè•°£Ö¦ó%ahVˆ¶ëbá±OÒ��ÜQðd_©ü¯‹µ±˜™ä½ª@ÂæQˆ3Wøò‰�­¿¡JE‘©ÇIhQFE™Ô�¡XãÇI÷ùskC¼(3rè¶*;çk‡›?Û`YÜðœÆÁ€�jž¤žÙwÿˆþ‡WýÀ¾k2“²¶9Ò�³„çüÃð›(»Öæ\©F±¹yÂ8”~:@RÚôõ¼èÐucÇ(ÄýÄ©"¶|….î4qº�·ð¸åf•5¾?ÒLh7f!m°W
�µM\QN®¶(nßÐ˜ˆçävŒ±1tÂýÚ“êbî¢²d5êÊH�økZ¡^Íƒh»Ò²»Æ7P½.“^LÂø+ÏMI„ûí5÷„Ùëªf`Ò‚ä\CÂžß™¦ßH3§!e¿ÿÕÝD-“a »…Ç<Ö“X:ù=ð»»‘KŒ´©ÓE�÷S8Wl—Ž%3 Î½=þ÷°�Aê0Zmb»¡ëk`G}%ÿ6rÙ+6æQ´¸{Qï[}`:{\,n¡eã"Å]º´-l=¯�ž7ÏMV{G7Î’½¤!$Î÷‰‡ õ£•ÎžßÌk�W{‘³hJ})ÕF‰Š…bÕ$TD1$$!_¬á*ÒT¤§¯Ÿ*#zŸrÏ¿›Ãô N`}34ÌeQ(Ój~lhÅµ>·Âæ(Çñj«"zLu‰¡/Ï4y®˜ôçÁ»‚šö²P’¡—´8¹Ù+ñ”Ù7[{h^²Â€·³ ]r4ª%0MB
ScMrƒ:Q/éðóÔŸHe‚â\Èì£†È«kÁDÓ„¼fg¶MRn…NL‘€0('”'Iâ�íƒù\>48<¹�ðöë£„$³�pÙbŽ¼ØY¹fB�æ§±ùQõAÒŒOŒÍ%<p<î¤áüZ`¡˜�BMø) ÝHš9q1yDW¢¶ÓZ…[ò"M:_�ñihWq�Ã3ðRLvå!Ö�<üq¿æbÆEœ3Ø î/yáVy€O‡É&÷k1C¢U+4k%ý„ëy�©kWLg@ Þ¯_»†"–7Ø½×Ô:¸. ®¢‰FgF–�Æ3µ¨/åðÞ—ù…ù^wÇ:ðÙ¹Ài@{>ûà†x@ëá¤¼•åïKªÅ7ôk»”µ¶�æ"Y˜N
Çís/¼ÒT) élµì®0K�lÀ(çcw5ÔÊª-tÀ˜.Â”ùºQÍ®Î}Þ¨Æ~»’l¤ß©ýtÖ4ÕßW-É\çÕ¼ûØZÈ�L¥ŒWÛF«SÃÅéö™ÎIªˆ¥Ê ò˜œ¸Á š&k7±ÐYÇbüèoq™3
šV"%åahjTÈAÔÇlÅÍ¿¼J,˜lÏTÿ>oÜÎ¿‰ã!–;hß*úó°ØD•'ãÛ†ã…4’œOÝ«=aÍyÇÌ‘0ºK+›E²Ûßõni-X% Žñû¿Æ³Å#7¡àÒFþdÅA{s€m–pô>ÑCcËlIQ) \g�ü‚†J¹Ûë;»™\�ê!¡±™°=• #ÉÔsgWtöyÝ‚äPVlº¾e0ñíâ
')Š—ƒwÖ©e‹*3×ÐY5¦T
#P÷�!W�ôYåZ` ÷‘¯�ô G~µ3ýBd^,�o›ß]ôªŸž1Ø]×ÈÓuØ¤µ‡‹Hœ‚SÚ&n©ÝÐP1+_7ºAŽsu‹>Ýþ†6Å�aŠâm)g)ŠH/u‘8Ž3Ýk]šâF2a¸º×’k,g½å˜7 &‰jU½i±†Iè�ÊSÜ¬ô¯7%@¹Å—¸�OÔEšO]…ntÓJòŠã�Xn¨™Á×lQ$A{Ç8}º×¤p{2a¾ï›‰ØZÓÖéè;ü`(�®SWô}Ë­ h=DpèìéWžùÛògsIÑÐ® ´è÷…§7 qÔº›rWhÕ Â�ã|1¥-ê†­ïíòP*,B¨íŸ,†	³ìÇ×bþw?2yû)ÔiÈòþGy9QÉ›EC¼¦yAý®4Ô‹ß­6ìÁ–¢†¨65©>ùÍÅ7ÑöÂƒ*ñ<„KÂ”XÊž"t¡£|�u‰º×‰d'�ã¡½Ë6d¦Z
œü‰KÃóúª¢5Lâ©©båÄŒöÙsž¹6qz*vÍÙn+—zÇ&Å?Ùc8Sþd|@fo¾ï#ÜB[AÁE°‹öà(÷²ç“ gñ1íC�Ež)è?…îçÿ¨j°KÜ Tý2u3jjœI
o¼ŠÊJ¹Ô«  ~‰®#Ï´ã»·ñ
L3HÒ3"€‚!’&};sí©‘!_�Ew_èk`’íGêÊBR°{+ßmDy u™Š«–¨ÑÓŠl>)}1*§‡Të:¾é�=Ì8ìI}[&Ã¹Ü$Åp€³á··1’?Üos~:ÁÄ!¸®³z.·ð%½8îíð*.æj¤NMŸ˜ŸÁqo ºÿV~gRÆ-xF¦á¨×¨
¬¾#1ŠÈ$¼?”Òì]°´XHp
ß QÒÂðí9É‰zMæA7	ÚôF¶"gVDÏŠñ�Ûz žLõ:é[ÓC-�€W°ô0…èØ2àíâÐ­Cy„”‰Döì/åR¢’²¥ó)9[®‰DþØoÃ¡)i¦�Õ*°³W”‚‰Ö8È.Gì²8¹+zU=còírÉúð½+É°Šyú—i×¾pË;¶Õ'ýsRûv¯ƒ»õ”¹0U“ÐJÐ*Ú�zÞ’á›±�îº_;9üŒ¦1Ê
dtY�t�4èT�îÒ£`ÛòØ¬=P+Ãùü¨–ÁUðÇª8ÏêatMX2\C·ë­bÝ�0”¥h«sz3j{ #ÚvÈu�åÃR·ÌªÜô‰LÒ†ÅÅ–/ùãp
ÎÔÒ¤0Ì¥ü%zÅ‡¡Ä§‘	Ó1kUt†Ší5ø½¹’I<®ÂþÈ¶škKãŒ”ÅûJ†u m¬kê—^:ÊR&ªY~§§ qFˆ*FÂ×±¨ƒ‘²/çì­í\%VL*Bï­ˆÚIÌ¶ÑUÛæÖN5s·ìÒßë%Êo<3Ñø6oJ‰­ºÈž â1c>ÉD|£/QÉ9(YÎ)ˆ�2š�–ßìtvÛe6	 IX~Åè9”�u0Jqy,V¾„ïÈÆA.ÂqT²óxs6[OçëÕÚŒ³DS„_S8„“
]� Ú`EÜèao›I>#_1áX¹d5(\WW ÇÅ‚¶çÂZí¼/öØŒ~…o8ÍÅl¯ï.T¾„ÕÐ>T¼¸Hƒ|kQBfà[Ì=táçŒÇ´�§°¸ÏÁÙl†„k0£Žõ³<x
:œÛ¢Ü™Ya_º!7µ7LôvxTð@¤³-ïWjû§æ:uÛ.ˆÔíV58Ì<
w"Ë4éûâ2„¬÷JÞPŸ97ÌÞÀ¸3³‚DëME°½Éq\x/ZªPïb"mÿtjÈ'SÏÓ1‘^ÏÀå_^z™­:ÿñî�_ûõ ðîìgq¶0Úçnå_úO¶5yñù];®²M]*zÄ`Mí©—^©Øæ©ÖQ/]y¿üE š—ÏMðwdUnS¾ÉöØ[ê(µœ'ÏùSKUÝ§L•‡t÷;£é 	Ñ¯ïiÎ[<ûXˆ»µ74ËS^ËÉ¯øÜ�}Gc¯ìv#ÓÔ¥µT6*ess=address,notes='Verify measurements on site. Side gate access by arrangement.')
            customers.append(customer);properties.append(prop)
        for i,(name,stage,source,value,offset,service) in enumerate([
            ('Jamie Rivera','new','Facebook',285000,0,'Permanent lighting'),
            ('Quinn Hayes','new','Referral',420000,1,'Permanent lighting'),
            ('Alex Mercer','contacted','Website',310000,-1,'Permanent lighting'),
            ('Cameron Wells','visit_scheduled','Direct',165000,2,'Cameras'),
            ('Rowan Blake','estimate_sent','Facebook',375000,1,'Permanent lighting'),
            ('Emery Woods','won','Referral',240000,0,'Permanent lighting'),
            ('Finley Carter','lost','Website',180000,0,'Permanent lighting')]):
            put('leads',name=name,email=f'lead{i+1}@example.test',phone=f'419-555-01{i+30:02}',address=f'{200+i*8} Sample Court, Demo City, OH',
                service=service,stage=stage,source=source,value_cents=value,follow_up=day(offset) if stage not in ('won','lost') else '',
                notes='Sample inquiry. Customer is interested in a front roofline estimate.')
        for i in range(11):
            customer=customers[i%8];prop=properties[i%8]
            status='accepted' if i<7 else ('sent' if i<10 else 'draft')
            payload=Estimate(customer_id=customer['id'],property_id=prop['id'],title=['Front roofline lighting','Full exterior lighting','Roofline + garage lighting'][i%3],
                items=[dict(service_id=services[0]['id'],description=services[0]['name'],quantity=110+i*17,unit='foot',unit_price='18',unit_cost='8.50'),
                       dict(service_id=services[1]['id'],description=services[1]['name'],quantity=1,unit='each',unit_price='165',unit_cost='85')],
                tax_percent=0,deposit_percent=30,expires_on=day(30),notes='Sample proposal only. Verify scope, measurements, equipment and applicable tax before sending a real estimate.')
            totals=calculate(payload);totals['items_json']=json.dumps(totals.pop('items'))
            est=put('estimates',customer_id=customer['id'],property_id=prop['id'],code=next_code(conn,'estimates',bid,'EST'),title=payload.title,
                status=status,notes=payload.notes,expires_on=payload.expires_on,approval_note='Demo approval recorded by phone.' if status=='accepted' else '',
                accepted_at=now() if status=='accepted' else None,**totals)
            if status=='accepted':
                job_status=['completed','completed','completed','in_progress','scheduled','scheduled','approved'][i]
                scheduled=day([-80,-49,-18,0,2,4,7][i])
                job=put('jobs',customer_id=customer['id'],property_id=prop['id'],estimate_id=est['id'],code=next_code(conn,'jobs',bid,'JOB'),title=est['title'],
                    status=job_status,scheduled_date=scheduled if job_status!='approved' else '',due_date='',assigned_user_id=members[i%2]['id'],
                    notes='Confirm access with customer before arrival. Record final wire routes and controller location.',
                    checklist_json=json.dumps([dict(text=text,done=(i<3 or j<2)) for j,text in enumerate(['Confirm access and measurements','Prepare materials','Complete installation','Test with customer','Upload completion photos'])]))
                public_items=[{k:v for k,v in item.items() if k not in ('unit_cost_cents','cost_cents','service_id')} for item in json.loads(est['items_json'])]
                inv=put('invoices',customer_id=customer['id'],job_id=job['id'],estimate_id=est['id'],code=next_code(conn,'invoices',bid,'INV'),title=est['title'],status='open',
                    items_json=json.dumps(public_items),subtotal_cents=est['subtotal_cents'],discount_cents=0,tax_cents=0,total_cents=est['total_cents'],due_date=day(14 if i>2 else -10))
                if i<5:
                    put('transactions',kind='payment',invoice_id=inv['id'],job_id=job['id'],amount_cents=est['total_cents'] if i<3 else est['deposit_cents'],
                        date=scheduled if i<3 else day(),category='Customer payment',method='Bank transfer' if i%2 else 'Check',description='Final payment' if i<3 else 'Installation deposit',reference='SAMPLE')
                put('transactions',kind='expense',invoice_id=None,job_id=job['id'],amount_cents=80000+i*11000,date=day(-5 if i>2 else [-80,-49,-18][i]),
                    category='Materials',method='Business card',description='Lighting materials - sample expense',reference='SAMPLE')
        put('transactions',kind='expense',invoice_id=None,job_id=None,amount_cents=6500,date=day(),category='Overhead',method='Business card',description='Sample business overhead',reference='SAMPLE')
        for action,entity,detail in [('Created sample workspace','businesses','All records and prices are fictional.'),('Prepared estimate','estimates','Front roofline lighting'),('Scheduled installation','jobs','Upcoming lighting installation'),('Recorded deposit','transactions','Sample customer payment')]:
            audit(conn,dict(admin),bid,action,entity,bid,detail)
    return bid


def main():
    parser=argparse.ArgumentParser(description='LB Exterior Lighting CRM administration')
    parser.add_argument('command',choices=['init','demo','reset-admin'])
    args=parser.parse_args()
    try:
        if args.command=='init':
            print('\nCreate your PLATFORM ADMINISTRATOR. No default password is installed.\n')
            name=input('Your name: ').strip() or 'Platform Admin'
            email=input('Administrator email: ').strip()
            password=getpass.getpass('Password (at least 12 characters): ')
            if getpass.getpass('Repeat password: ')!=password:
                raise ValueError('Passwords did not match.')
            bootstrap(name,email,password)
            print('\nAdministrator created. LB Exterior Lighting is ready as an empty business workspace.')
            if input('Create a SEPARATE fictional demo workspace? [Y/n]: ').strip().lower()!='n':
                seed_demo();print('Demo workspace created separately from your real business.')
        elif args.command=='demo':
            print('Demo business:',seed_demo())
        else:
            initialize();email=input('Administrator email: ').strip().lower()
            password=getpass.getpass('New password (at least 12 characters): ')
            if getpass.getpass('Repeat password: ')!=password:
                raise ValueError('Passwords did not match.')
            with db(write=True) as conn:
                row=conn.execute('SELECT * FROM users WHERE email=? AND is_super=1',(email,)).fetchone()
                if not row: raise ValueError('Platform administrator not found.')
                conn.execute('UPDATE users SET password_hash=?,active=1,must_change=0 WHERE id=?',(hash_password(password),row['id']))
                conn.execute('DELETE FROM sessions WHERE user_id=?',(row['id'],))
                audit(conn,dict(row),None,'Recovered admin account on server','users',row['id'])
            print('Administrator password reset. Existing sessions have been revoked.')
    except (ValueError,sqlite3.IntegrityError) as exc:
        parser.exit(1,f'Error: {exc}\n')


if __name__=='__main__': main()
