File size: 13,720 Bytes
72eef4f
 
 
 
 
 
6af31ea
 
 
72eef4f
 
6af31ea
 
72eef4f
6af31ea
 
 
 
72eef4f
6af31ea
72eef4f
6af31ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72eef4f
6af31ea
72eef4f
6af31ea
 
 
 
 
 
 
 
 
72eef4f
6af31ea
72eef4f
 
6af31ea
 
72eef4f
6af31ea
72eef4f
6af31ea
 
72eef4f
 
 
 
 
 
 
6af31ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72eef4f
6af31ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72eef4f
6af31ea
 
 
 
72eef4f
6af31ea
 
 
 
 
72eef4f
 
 
 
6af31ea
72eef4f
 
6af31ea
72eef4f
 
6af31ea
72eef4f
6af31ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72eef4f
6af31ea
 
72eef4f
6af31ea
 
 
72eef4f
 
6af31ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72eef4f
6af31ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72eef4f
 
6af31ea
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
import logging
import threading
from datetime import datetime
from flask import current_app
from bson.objectid import ObjectId

# Import the new REST API client helper
from .xero_client import make_zoho_api_request
from .extensions import mongo

logger = logging.getLogger(__name__)

def sync_user_approval_from_zoho():
    """

    Updates users' 'is_approved' status based on the 'cf_approval_status'

    custom dropdown in Zoho Books. A contact is considered approved when:

        custom_field.label == 'cf_approval_status' and custom_field.value == 'approved'

    This function pages through contacts and inspects custom_fields for the match.

    """
    logger.info("Starting Zoho Books 'cf_approval_status' custom field sync.")
    try:
        page = 1
        per_page = 200  # Zoho typically supports up to 200 per page; adjust if necessary
        approved_contact_ids = set()

        while True:
            params = {'page': page, 'per_page': per_page}
            response = make_zoho_api_request('GET', '/contacts', params=params)

            if not response:
                logger.warning(f"No response from Zoho when fetching contacts page {page}. Stopping pagination.")
                break
            logger.info(response)

            contacts = response[0]['contacts'] or []
            if not contacts:
                logger.info(f"No contacts returned on page {page}. Pagination complete.")
                break

            for contact in contacts:
                # Contact id field might be 'contact_id' or similar - defensive access
                contact_id = contact.get('email') or contact.get('contactId') or contact.get('id')
                # Ensure we have a custom_fields list to check
                custom_fields = contact.get('custom_fields') or []
                for cf in custom_fields:
                    # cf typically has keys like 'label' and 'value' when you use label-based assignment
                    if cf.get('label') == 'cf_approval_status' and str(cf.get('value')).lower() == 'approved':
                        if contact_id:
                            approved_contact_ids.add(str(contact_id))
                        break  # no need to check other custom fields for this contact

            # If response contains page context we can use it; otherwise continue until an empty page
            page_context = response[0]['page_context'] 
            has_more = page_context.get('has_more_page')
            if has_more is None:
                # fallback: stop when we received fewer than per_page results
                if len(contacts) < per_page:
                    break
                page += 1
            else:
                if not has_more:
                    break
                page += 1

        logger.info(f"Found {len(approved_contact_ids)} approved contacts in Zoho Books (cf_approval_status == 'approved').")

        # Set all users to not approved first (only users with a zoho_contact_id are considered)
        # mongo.db.users.update_many({'zoho_contact_id': {'$exists': True}}, {'$set': {'is_approved': False}})

        # Then, set the ones found in the sync to approved
        if approved_contact_ids:
            mongo.db.users.update_many(
                {'email': {'$in': list(approved_contact_ids)}},
                {'$set': {'is_approved': True}}
            )

        logger.info(f"Zoho approval sync complete. {len(approved_contact_ids)} users are now marked as approved.")

    except Exception as e:
        logger.error(f"An error occurred during Zoho user approval sync: {e}", exc_info=True)


def create_zoho_contact_async(app_context, registration_data):
    """

    Creates a contact in Zoho Books, sets a 'pending' custom field, and adds a note (comment).

    Assumes a custom field exists whose label is 'cf_approval_status' (adjust label/index if needed).

    """
    with app_context:
        user_email = registration_data.get('email')
        try:
            contact_person_name = registration_data.get('contactPerson', '')
            first_name, last_name = (contact_person_name.split(' ', 1) + [''])[:2]

            contact_payload = {
                'contact_name': registration_data.get('businessName'),
                'contact_type': 'customer',
                'company_name': registration_data.get('companyName') ,
                'contact_persons': [{
                    'first_name': first_name,
                    'last_name': last_name,
                    'email': user_email,
                    'phone': registration_data.get('phoneNumber'),
                    # keep this: it's a valid flag for the contact person entry
                    'is_primary_contact': True
                }],
                'billing_address': {
                    'address': registration_data.get('businessAddress', 'N/A')
                },
                'shipping_address': {
                    'address': registration_data.get('businessAddress', 'N/A')
                },
                'website': registration_data.get('companyWebsite'),
                # <-- Use label/index + value (not api_name)
                'custom_fields': [
                    {
                        'label': 'cf_approval_status',       # must match the field label in Zoho Books
                        # 'index': 1,                        # optional: set if you know the slot (1..10)
                        'value': 'pending_for_approval'     # value must match one of the dropdown option values
                    }
                ]
            }

            response = make_zoho_api_request('POST', '/contacts', json_data=contact_payload)

            # defensive logging: log whole response so you can inspect Zoho's error message if any
            logger.debug(f"Zoho create contact response: {response}")

            # # basic success check — adjust depending on your make_zoho_api_request return structure
            # if not response or 'contact' not in response:
            #     logger.error(f"Zoho Books contact creation failed for {user_email}: {response}")
            #     return

            new_contact_id = response[0]['contact']['contact_id']
            logger.info(f"Successfully created Zoho Books contact ({new_contact_id}) for user {user_email}.")

            mongo.db.users.update_one({'email': user_email}, {'$set': {'zoho_contact_id': new_contact_id}})

            history_details = (
                f"--- Client Application Details ---\n"
                f"Business Name: {registration_data.get('businessName')}\n"
                f"Contact Person: {registration_data.get('contactPerson')}\n"
                f"Email: {registration_data.get('email')}\n"
                f"Phone: {registration_data.get('phoneNumber')}\n"
                f"Company Website: {registration_data.get('companyWebsite')}\n"
                f"Business Address: {registration_data.get('businessAddress')}\n"
                f"Business Type: {registration_data.get('businessType')}\n"
                f"Years Operating: {registration_data.get('yearsOperating')}\n"
                f"Number of Locations: {registration_data.get('numLocations')}\n"
                f"Estimated Weekly Volume: {registration_data.get('estimatedVolume')}\n\n"
                
                f"--- Logistics Information ---\n"
                f"Preferred Delivery Times: {registration_data.get('preferredDeliveryTimes')}\n"
                f"Has Loading Dock: {registration_data.get('hasLoadingDock')}\n"
                f"Special Delivery Instructions: {registration_data.get('specialDeliveryInstructions')}\n"
                f"Minimum Quantity Requirements: {registration_data.get('minQuantityRequirements')}\n\n"
                
                f"--- Service & Billing ---\n"
                f"Reason for Switching: {registration_data.get('reasonForSwitch')}\n"
                f"Preferred Payment Method: {registration_data.get('paymentMethod')}\n\n"
                
                f"--- Additional Notes ---\n"
                f"{registration_data.get('additionalNotes')}"
            )
            comment_payload = {
                'description': history_details,
                'show_comment_to_clients': False
            }

            make_zoho_api_request('POST', f'/contacts/{new_contact_id}/comments', json_data=comment_payload)
            logger.info(f"Successfully added registration details as a comment to Zoho contact {new_contact_id}.")

        except Exception as e:
            logger.error(f"Failed during Zoho Books contact/comment creation for user {user_email}. Error: {e}", exc_info=True)



def trigger_contact_creation(registration_data):
    """Starts a background thread to create a Zoho contact."""
    try:
        app_context = current_app.app_context()
        thread = threading.Thread(target=create_zoho_contact_async, args=(app_context, registration_data))
        thread.daemon = True
        thread.start()
        logger.info(f"Started Zoho contact creation thread for {registration_data.get('email')}")
    except Exception as e:
        logger.error(f"Failed to start Zoho contact creation thread. Error: {e}")
def get_zoho_contact_by_email(email):
    """Fetches a Zoho contact ID by email."""
    try:
        # Assumes make_zoho_api_request is a helper function that handles authentication
        response = make_zoho_api_request('GET', '/contacts', params={'email': email})
        contacts = response[0]['contacts']
        if contacts:
            return contacts[0]['contact_id']
        else:
            logger.info(f"No Zoho contact found for email: {email}")
            return None
    except Exception as e:
        logger.error(f"Error fetching Zoho contact for {email}: {e}", exc_info=True)
        return None

def create_zoho_invoice_async(app_context, order_details):
    """Creates an Invoice in Zoho Books from order details (CAD currency, order no -> reference_number)."""
    with app_context:
        user_email = order_details['user_email']
        contact_id = get_zoho_contact_by_email(user_email)

        if not contact_id:
            logger.error(f"Cannot create Invoice. Zoho contact not found for email {user_email}.")
            return


        line_items = []
        for item in order_details['items']:
            product = mongo.db.products.find_one({'_id': ObjectId(item['productId'])})
            for mode in product.get('modes', []):
                if str(mode) == item['mode']:
                    product['zoho_id'] = product.get('modes')[mode].get('zoho_id')
                    product['price'] = product.get('modes')[mode].get('price')
                    break

            logger.info(f"Processing item {product.get('zoho_id')} with mode {item['mode']} for invoice creation.")
            unit = "lb" if item.get("mode") == "weight" else item.get("mode")
            line_items.append({
                'item_id': product['zoho_id'],
                'quantity': int(item['quantity']),
                'rate': float(product.get('price', 0)),
                'description': f"{item['quantity']} {unit} of {product.get('name', 'N/A')}"
            })

        if not line_items:
            logger.error("Zoho Invoice failed: No valid line items for order %s", order_details['order_id'])
            return

        # build invoice payload with CAD and order number
        logger.info( order_details.get('additional_info', 'N/A'))
        invoice_payload = {
            'customer_id': contact_id,
            'date': datetime.strptime(order_details.get("order_date", order_details["deliverydate"]), "%Y-%m-%d").strftime("%Y-%m-%d"),
            'due_date': datetime.strptime(order_details["deliverydate"], "%Y-%m-%d").strftime("%Y-%m-%d"),
            'line_items': line_items,
            'notes': order_details.get('additional_info', 'N/A'),
            'billing_address': {
                'address': order_details.get('delivery_address')
            },

            # <--- currency & order mapping
            'currency_code': 'CAD',          # invoice currency = Canadian Dollars
            'exchange_rate': 1.0,            # set to appropriate rate (1.0 if you treat amounts as CAD already)
            'reference_number': order_details['order_id'],  # shows your order number on the invoice

            # Optionally add custom fields if you have created one and know its customfield_id
            # 'custom_fields': [
            #     {'customfield_id': 123456789012345, 'value': order_details['order_id']}
            # ]
        }

        # send to Zoho
        response = make_zoho_api_request('POST', '/invoices', json_data=invoice_payload)
        print(response)
        invoice_id = response['invoice']['invoice_id']
        logger.info(f"Successfully created Zoho Books Invoice {invoice_id} for order ID: {order_details['order_id']}")


def trigger_invoice_creation(order_details):
    """Starts a background thread to create a Zoho Invoice."""
    try:
        app_context = current_app.app_context()
        thread = threading.Thread(target=create_zoho_invoice_async, args=(app_context, order_details))
        thread.daemon = True
        thread.start()
        logger.info(f"Started Zoho Invoice creation thread for order {order_details.get('order_id')}")
    except Exception as e:
        logger.error(f"Failed to start Zoho Invoice creation thread for order %s. Error: %s", order_details.get('order_id'), e)