def make_dimension_in_accounting_doctypes(doc): doclist = get_doctypes_with_dimensions() doc_count = len(get_accounting_dimensions()) count = 0 for doctype in doclist: if (doc_count + 1) % 2 == 0: insert_after_field = 'dimension_col_break' else: insert_after_field = 'accounting_dimensions_section' df = { "fieldname": doc.fieldname, "label": doc.label, "fieldtype": "Link", "options": doc.document_type, "insert_after": insert_after_field, "owner": "Administrator" } if doctype == "Budget": add_dimension_to_budget_doctype(df, doc) else: meta = frappe.get_meta(doctype, cached=False) fieldnames = [d.fieldname for d in meta.get("fields")] if df['fieldname'] not in fieldnames: create_custom_field(doctype, df) count += 1 frappe.publish_progress(count * 100 / len(doclist), title=_("Creating Dimensions...")) frappe.clear_cache(doctype=doctype)
def assign_salary_structure_for_employees(employees, salary_structure, payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None): salary_structures_assignments = [] existing_assignments_for = get_existing_assignments( employees, salary_structure, from_date) count = 0 for employee in employees: if employee in existing_assignments_for: continue count += 1 salary_structures_assignment = create_salary_structures_assignment( employee, salary_structure, payroll_payable_account, from_date, base, variable, income_tax_slab) salary_structures_assignments.append(salary_structures_assignment) frappe.publish_progress( count * 100 / len(set(employees) - set(existing_assignments_for)), title=_("Assigning Structures...")) if salary_structures_assignments: frappe.msgprint(_("Structures have been assigned successfully"))
def create_salary_slips_for_employees(employees, args, publish_progress=True): salary_slips_exists_for = get_existing_salary_slips(employees, args) count = 0 for emp in employees: if emp not in salary_slips_exists_for: args.update({"doctype": "Salary Slip", "employee": emp}) ss = frappe.get_doc(args) ss.insert() count += 1 if publish_progress: frappe.publish_progress( count * 100 / len(set(employees) - set(salary_slips_exists_for)), title=_("Creating Salary Slips...")) else: salary_slip_name = frappe.db.sql('''SELECT name FROM `tabSalary Slip` WHERE company=%s AND start_date >= %s AND end_date <= %s AND employee = %s ''', (args.company, args.start_date, args.end_date, emp), as_dict=True) salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name) salary_slip_doc.exchange_rate = args.exchange_rate salary_slip_doc.set_totals() salary_slip_doc.db_update() payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) payroll_entry.db_set("salary_slips_created", 1) payroll_entry.notify_update()
def create_salary_slips_for_employees(employees, args, publish_progress=True): salary_slips_exists_for = get_existing_salary_slips(employees, args) count = 0 salary_slips_not_created = [] for emp in employees: if emp not in salary_slips_exists_for: args.update({"doctype": "Salary Slip", "employee": emp}) ss = frappe.get_doc(args) ss.insert() count += 1 if publish_progress: frappe.publish_progress( count * 100 / len(set(employees) - set(salary_slips_exists_for)), title=_("Creating Salary Slips...")) else: salary_slips_not_created.append(emp) payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) payroll_entry.db_set("salary_slips_created", 1) payroll_entry.notify_update() if salary_slips_not_created: frappe.msgprint(_( "Salary Slips already exists for employees {}, and will not be processed by this payroll." ).format( frappe.bold(", ".join([emp for emp in salary_slips_not_created]))), title=_("Message"), indicator="orange")
def create_bank_entries(columns, data, bank_account): bank_name = frappe.db.get_value("Bank Account", bank_account, "bank") bank = frappe.get_doc("Bank", bank_name) header_map = get_header_mapping(columns, bank) success = 0 errors = 0 total = len(json.loads(data)) for idx, d in enumerate(json.loads(data)): if all(item is None for item in d) is True: continue fields = {} for key, value in iteritems(header_map): fields.update({ key: get_value(d, key, bank, int(value)-1) }) frappe.publish_progress(((idx+1)*100)/total, title=_("Importing Transactions"),description=_("Transaction {0} of {1}.").format(idx, total)) try: bank_transaction = frappe.get_doc({ "doctype": "Bank Transaction" }) bank_transaction.update(fields) bank_transaction.date = getdate(parse_date(bank_transaction.date)) bank_transaction.bank_account = bank_account bank_transaction.insert() bank_transaction.submit() success += 1 except Exception: frappe.log_error(frappe.get_traceback()) errors += 1 return {"success": success, "errors": errors}
def make_dimension_in_accounting_doctypes(doc): doclist = get_doctypes_with_dimensions() doc_count = len(get_accounting_dimensions()) count = 0 for doctype in doclist: if (doc_count + 1) % 2 == 0: insert_after_field = 'dimension_col_break' else: insert_after_field = 'accounting_dimensions_section' df = { "fieldname": doc.fieldname, "label": doc.label, "fieldtype": "Link", "options": doc.document_type, "insert_after": insert_after_field } if frappe.db.exists("DocType", doctype): if doctype == "Budget": add_dimension_to_budget_doctype(df, doc) else: create_custom_field(doctype, df) count += 1 frappe.publish_progress(count * 100 / len(doclist), title=_("Creating Dimensions...")) frappe.clear_cache(doctype=doctype)
def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True): submitted_ss = [] not_submitted_ss = [] frappe.flags.via_payroll_entry = True count = 0 for ss in salary_slips: ss_obj = frappe.get_doc("Salary Slip",ss[0]) if ss_obj.net_pay<0: not_submitted_ss.append(ss[0]) else: try: ss_obj.submit() submitted_ss.append(ss_obj) except frappe.ValidationError: not_submitted_ss.append(ss[0]) count += 1 if publish_progress: frappe.publish_progress(count*100/len(salary_slips), title = _("Submitting Salary Slips...")) if submitted_ss: payroll_entry.make_accrual_jv_entry() frappe.msgprint(_("Salary Slip submitted for period from {0} to {1}") .format(ss_obj.start_date, ss_obj.end_date)) payroll_entry.email_salary_slip(submitted_ss) payroll_entry.db_set("salary_slips_submitted", 1) payroll_entry.notify_update() if not submitted_ss and not not_submitted_ss: frappe.msgprint(_("No salary slip found to submit for the above selected criteria OR salary slip already submitted")) if not_submitted_ss: frappe.msgprint(_("Could not submit some Salary Slips"))
def update(doctype, field, value, condition='', limit=500): if not limit or cint(limit) > 500: limit = 500 if condition: condition = ' where ' + condition if ';' in condition: frappe.throw(_('; not allowed in condition')) items = frappe.db.sql_list('''select name from `tab{0}`{1} limit 0, {2}'''.format(doctype, condition, limit), debug=1) n = len(items) for i, d in enumerate(items): doc = frappe.get_doc(doctype, d) doc.set(field, value) try: doc.save() except Exception as e: frappe.msgprint(_("Validation failed for {0}").format(frappe.bold(doc.name))) raise e frappe.publish_progress(float(i)*100/n, title = _('Updating Records'), doctype='Bulk Update', docname='Bulk Update') # clear messages frappe.local.message_log = [] frappe.msgprint(_('{0} records updated').format(n), title=_('Success'), indicator='green')
def update(doctype, field, value, condition='', limit=500): if not limit or cint(limit) > 500: limit = 500 if condition: condition = ' where ' + condition if ';' in condition: frappe.throw('; not allowed in condition') items = frappe.db.sql_list( '''select name from `tab{0}`{1} limit 0, {2}'''.format( doctype, condition, limit), debug=1) n = len(items) for i, d in enumerate(items): doc = frappe.get_doc(doctype, d) doc.set(field, value) doc.save() frappe.publish_progress(float(i) * 100 / n, title=_('Updating Records'), doctype='Bulk Update', docname='Bulk Update') # clear messages frappe.local.message_log = [] frappe.msgprint(_('{0} records updated').format(n), title=_('Success'), indicator='green')
def import_orders_from_neos_server(self): NEOSConnect_settings = frappe.get_doc("NEOSConnect Settings") ftp = FTP(NEOSConnect_settings.ftp_host) ftp.login(NEOSConnect_settings.ftp_user, NEOSConnect_settings.ftp_pass) filenames = ftp.nlst() to_do_files = [] for filename in filenames: if filename.startswith("order_") & filename.endswith(".csv"): to_do_files.append(filename) path = (frappe.utils.get_files_path(is_private=1)) + "/neosconnect" if not os.path.exists(path): os.makedirs(path) #gefundene Dateien Verarbeiten run_count = 0 for to_do_file in to_do_files: run_count += 1 percent = run_count * 100 / len(to_do_files) frappe.publish_progress(percent, "verarbeite Dateien") local_filename = path + "/" + to_do_file if os.path.isfile(local_filename): os.remove(local_filename) file_writer = open(local_filename, 'wb') ftp.retrbinary('RETR ' + to_do_file, file_writer.write) file_writer.close() ftp.delete(to_do_file) self.process_file(to_do_file, path, NEOSConnect_settings, True) os.remove(local_filename) ftp.quit() pass
def update(doctype, field, value, condition='', limit=500): if not limit or cint(limit) > 500: limit = 500 if condition: condition = ' where ' + condition if ';' in condition: frappe.throw('; not allowed in condition') items = frappe.db.sql_list( '''select name from `tab{0}`{1} limit 0, {2}'''.format( doctype, condition, limit), debug=1) n = len(items) for i, d in enumerate(items): doc = frappe.get_doc(doctype, d) doc.set(field, value) try: doc.save() except Exception, e: frappe.msgprint( _("Validation failed for {0}").format(frappe.bold(doc.name))) raise e frappe.publish_progress(float(i) * 100 / n, title=_('Updating Records'), doctype='Bulk Update', docname='Bulk Update')
def show_progress(docnames, message, i, description): n = len(docnames) if n >= 10: frappe.publish_progress( float(i) * 100 / n, title = message, description = description )
def show_progress(docnames, message, i, description): n = len(docnames) if n >= 5: frappe.publish_progress( float(i) * 100 / n, title = message, description = description )
def update_variants(variants, template, publish_progress=True): total = len(variants) for count, d in enumerate(variants, start=1): variant = frappe.get_doc("Item", d) copy_attributes_to_variant(template, variant) variant.save() if publish_progress: frappe.publish_progress(count / total * 100, title=_("Updating Variants..."))
def update_variants(variants, template, publish_progress=True): count=0 for d in variants: variant = frappe.get_doc("Item", d) copy_attributes_to_variant(template, variant) variant.save() count+=1 if publish_progress: frappe.publish_progress(count*100/len(variants), title = _("Updating Variants..."))
def set_delivery_note_for_tickets(self): settings = frappe.get_doc("OTRSConnect Settings") ERPNext_fetched_tickets = frappe.get_all("OTRSConnect Ticket", filters={ "status": "fetched", "erpnext_customer": ("!=", "") }) print(len(ERPNext_fetched_tickets)) run_count = 0 for ticketname in ERPNext_fetched_tickets: percent = run_count * 100 / len(ERPNext_fetched_tickets) run_count += 1 frappe.publish_progress(percent, "verarbeite Tickets") ticket_doc = frappe.get_doc("OTRSConnect Ticket", ticketname) delivery_notes = frappe.get_all("Delivery Note", filters={ "title": settings.delivery_note_title, "customer": ticket_doc.erpnext_customer, "status": "Draft" }) if len(delivery_notes) == 0: delivery_note_doc = frappe.get_doc({ "doctype": "Delivery Note", "customer": ticket_doc.erpnext_customer, "title": settings.delivery_note_title, "status": "Draft", "company": frappe.get_doc("Global Defaults").default_company }) for item in self.get_items_for_delivery_note_from_articles( ticket_doc): delivery_note_doc.append("items", item) delivery_note_doc.insert() else: delivery_note_doc = frappe.get_doc("Delivery Note", delivery_notes[0]) for item in self.get_items_for_delivery_note_from_articles( ticket_doc): delivery_note_doc.append("items", item) delivery_note_doc.save() ticket_doc.status = "delivered" ticket_doc.save() ticket_doc.submit() frappe.msgprint(str(run_count) + " Tickets verarbeitet.")
def cancel_all_linked_docs(docs): """ Cancel all linked doctype Arguments: docs (str) - It contains all list of dictionaries of a linked documents. """ docs = json.loads(docs) for i, doc in enumerate(docs, 1): if validate_linked_doc(doc) is True: frappe.publish_progress(percent=i * 100 / len(docs), title=_("Cancelling documents")) linked_doc = frappe.get_doc(doc.get("doctype"), doc.get("name")) linked_doc.cancel()
def execute(doctype, name, party, lang=None): """ Queue calls this method, when it's ready. 1. Create necessary folders 2. Get raw PDF data 3. Save PDF file and attach it to the document """ settings = frappe.get_single("PDF on Submit Settings") show_progress = not settings.create_pdf_in_background progress_title = _("Creating PDF ...") if lang: frappe.local.lang = lang if show_progress: publish_progress(percent=0, title=progress_title) doctype_folder = create_folder(_(doctype), "Home") party_folder = create_folder(party, doctype_folder) if show_progress: publish_progress(percent=33, title=progress_title) pdf_data = get_pdf_data(doctype, name) if show_progress: publish_progress(percent=66, title=progress_title) save_and_attach(pdf_data, doctype, name, party_folder) if show_progress: publish_progress(percent=100, title=progress_title)
def assign_salary_structure_for_employees(employees, salary_structure,from_date=None, base=None,variable=None): salary_structures_assignments = [] existing_assignments_for = get_existing_assignments(employees, salary_structure.name,from_date) count=0 for employee in employees: if employee in existing_assignments_for: continue count +=1 salary_structures_assignment = create_salary_structures_assignment(employee, salary_structure, from_date, base, variable) salary_structures_assignments.append(salary_structures_assignment) frappe.publish_progress(count*100/len(set(employees) - set(existing_assignments_for)), title = _("Assigning Structures...")) if salary_structures_assignments: frappe.msgprint(_("Structures have been assigned successfully"))
def create_log(initiative_count, initiative_target, month, args, publish_progress=True): if frappe.db.sql( """select count(name) from `tabBSC Initiative Log` where docstatus < 2 and month = %s and bsc_initiative = %s""", (month, args.bsc_initiative))[0][0] == 0: log_args = frappe._dict({ "doctype": "BSC Initiative Log", "bsc_initiative": args.bsc_initiative, "bsc_target": args.bsc_target, "department": args.department, "fiscal_year": args.fiscal_year, "month": month, "log_target": initiative_target, "log_count": initiative_count, "employee": args.employee, }) il = frappe.get_doc(log_args) il.insert() # create the BSC Ledger Entry# ble = frappe.get_doc( frappe._dict({ "bsc_indicator": args.bsc_indicator, "bsc_target": args.bsc_target, "bsc_initiative": args.bsc_initiative, "entry_type": "Targeted", "month": month, "entry_number": initiative_target, "entry_count": initiative_count, "department": args.department, "fiscal_year": args.fiscal_year, "doctype": "BSC Ledger Entry" })) ble.insert() # if publish_progress: frappe.publish_progress( 100, title=_("Creating BSC Initiative Log for {0}...").format(month)) #bsc_initiative= frappe.get_doc("BSC Initiative", args.bsc_initiative) #bsc_initiative.db_set("initiative_logs_created", 1) #bsc_initiative.notify_update()
def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=[]): """ Cancel all linked doctype, optionally ignore doctypes specified in a list. Arguments: docs (json str) - It contains list of dictionaries of a linked documents. ignore_doctypes_on_cancel_all (list) - List of doctypes to ignore while cancelling. """ docs = json.loads(docs) if isinstance(ignore_doctypes_on_cancel_all, str): ignore_doctypes_on_cancel_all = json.loads(ignore_doctypes_on_cancel_all) for i, doc in enumerate(docs, 1): if validate_linked_doc(doc, ignore_doctypes_on_cancel_all): linked_doc = frappe.get_doc(doc.get("doctype"), doc.get("name")) linked_doc.cancel() frappe.publish_progress(percent=i/len(docs) * 100, title=_("Cancelling documents"))
def create_salary_slips_for_employees(employees, args, publish_progress=True): salary_slips_exists_for = get_existing_salary_slips(employees, args) count = 0 for emp in employees: if emp not in salary_slips_exists_for: args.update({"doctype": "Salary Slip", "employee": emp}) ss = frappe.get_doc(args) ss.insert() count += 1 if publish_progress: frappe.publish_progress( count * 100 / len(set(employees) - set(salary_slips_exists_for)), title=_("Creating Salary Slips...")) payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) payroll_entry.db_set("salary_slips_created", 1) payroll_entry.notify_update()
def import_from_csv_folder(self): NEOSConnect_settings = frappe.get_doc("NEOSConnect Settings") #CSV Headers, which we expect csv_headers = {} csv_headers['NEOS_note'] = [ "map_id", "man_name", "man_aid", "desc_short", "price_min", "price_special", "qty_status_max", "item_remarks", "user_name", "sup_name", "sup_id", "sup_aid", "price_amount", "qty_status", "item_qty", "vk_netto" ] csv_headers['NEOS_order'] = [ "map_id", "sup_name", "sup_id", "sup_aid", "man_name", "man_aid", "desc_short", "ean", "price_requested", "price_confirmed", "qty_requested", "qty_confirmed", "qty_delivered", "item_remark", "user_name", "reference", "customer_po", "order_name", "order_date", "response_date", "order_status" ] if NEOSConnect_settings.csv_import_folder != "": files = [] run_count = 0 files = os.listdir(NEOSConnect_settings.csv_import_folder) for file in files: run_count += 1 percent = run_count * 100 / len(files) frappe.publish_progress(percent, "verarbeite Dateien") current_file = os.path.join( NEOSConnect_settings.csv_import_folder, file) #NEOS Merkzettel if file.startswith("note_") & file.endswith(".csv"): files.append(current_file) self.process_csv(current_file, "NEOS_note", NEOSConnect_settings, csv_headers) #NEOS Bestellungen if file.startswith("order_") & file.endswith(".csv"): files.append(current_file) self.process_csv(current_file, "NEOS_order", NEOSConnect_settings, csv_headers) if len(files) == 0: frappe.throw("No files found in directory " + NEOSConnect_settings.csv_import_folder) else: frappe.throw("CSV directory error")
def set_ERPNext_OTRS_Tickets(self, closed_tickets_dict): run_count = 0 for ticket in closed_tickets_dict: run_count += 1 percent = run_count * 100 / len(closed_tickets_dict) frappe.publish_progress(percent, "verarbeite Tickets") ERPNext_tickets = frappe.get_all("OTRSConnect Ticket", filters={"id": ticket["id"]}) if len(ERPNext_tickets) == 0: frappe_doctype_dict = {"doctype": "OTRSConnect Ticket"} ticket["id"] = str(ticket["id"]) ticket["status"] = "fetched" frappe_doctype_dict.update(ticket) ticket_doc = frappe.get_doc(frappe_doctype_dict) inserted_ticket_doc = ticket_doc.insert() self.link_ERPNext_OTRS_Ticket(inserted_ticket_doc) self.set_ERPNext_OTRS_Articles( self.get_Articles_for_Ticket_dict(inserted_ticket_doc)) frappe.msgprint(str(run_count) + " Tickets verarbeitet.")
def create_salary_slips_for_employees(employees, args, publish_progress=True): salary_slips_exists_for = get_existing_salary_slips(employees, args) count=0 for emp in employees: if emp not in salary_slips_exists_for: args.update({ "doctype": "Salary Slip", "employee": emp }) ss = frappe.get_doc(args) ss.insert() count+=1 if publish_progress: frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)), title = _("Creating Salary Slips...")) payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) payroll_entry.db_set("salary_slips_created", 1) payroll_entry.notify_update()
def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=[]): """ Cancel all linked doctype Arguments: docs (str) - It contains all list of dictionaries of a linked documents. """ docs = json.loads(docs) if isinstance(ignore_doctypes_on_cancel_all, string_types): ignore_doctypes_on_cancel_all = json.loads( ignore_doctypes_on_cancel_all) for i, doc in enumerate(docs, 1): if validate_linked_doc(doc, ignore_doctypes_on_cancel_all) is True: frappe.publish_progress( percent=i * 100 / ((len(docs) - len(ignore_doctypes_on_cancel_all))), title=_("Cancelling documents")) linked_doc = frappe.get_doc(doc.get("doctype"), doc.get("name")) linked_doc.cancel()
def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leaves=0): leave_allocations = [] existing_allocations_for = get_existing_allocations(employees, leave_period.name) leave_type_details = get_leave_type_details() count=0 for employee in employees: if employee in existing_allocations_for: continue count +=1 leave_policy = get_employee_leave_policy(employee) if leave_policy: for leave_policy_detail in leave_policy.leave_policy_details: if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp: leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type, leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward_leaves) leave_allocations.append(leave_allocation) frappe.db.commit() frappe.publish_progress(count*100/len(set(employees) - set(existing_allocations_for)), title = _("Allocating leaves...")) if leave_allocations: frappe.msgprint(_("Leaves has been granted sucessfully"))
def create_salary_slips_and_payout(pp_list, args, publish_progress=True): #salary_slips_exists_for = get_existing_salary_slips(employees, args) count=0 for pp_row in pp_list: #if emp not in salary_slips_exists_for: args.update({ "doctype": "Salary Slip", "processed_payroll": pp_row["name"], "employee": pp_row["emp_id"] }) ss = frappe.get_doc(args) ss.insert() ss.submit() count+=1 processed_payroll = frappe.get_doc("Processed Payroll", pp_row["name"]) processed_payroll.db_set("pay_slip_status", 'Paid') processed_payroll.notify_update() if publish_progress: frappe.publish_progress(count * 100 / len(pp_list),title=_("Creating Salary Slips. It may take few minutes."))
def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0): leave_allocations = [] existing_allocations_for = get_existing_allocations(list(employee_records.keys()), leave_period.name) leave_type_details = get_leave_type_details() count = 0 for employee in employee_records.keys(): if employee in existing_allocations_for: continue count +=1 leave_policy = get_employee_leave_policy(employee) if leave_policy: for leave_policy_detail in leave_policy.leave_policy_details: if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp: leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type, leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward, employee_records.get(employee)) leave_allocations.append(leave_allocation) frappe.db.commit() frappe.publish_progress(count*100/len(set(employee_records.keys()) - set(existing_allocations_for)), title = _("Allocating leaves...")) if leave_allocations: frappe.msgprint(_("Leaves has been granted sucessfully"))
def create_targets_for_departments(dep_list, args, publish_progress=True): targets_exists_for = get_existing_targets(dep_list, args) count = 0 for dep in dep_list: if dep not in targets_exists_for: args.update({"doctype": "BSC Target", "department": dep}) ss = frappe.get_doc(args) ss.insert() count += 1 if publish_progress: frappe.publish_progress( count * 100 / len(set(dep_list) - set(targets_exists_for)), title=_("Creating BSC Target for {0} Department..." ).format(dep)) bsc_indicator = frappe.get_doc("BSC Indicator", args.bsc_indicator) bsc_indicator.db_set("create_count", bsc_indicator.create_count + 1) bsc_indicator.notify_update() if targets_exists_for: frappe.msgprint( _("Aleardy exist with {0}").format(', '.join( [str(dep) for dep in targets_exists_for])))
def send_statements(company=None, manual=None): """ Send out customer statements """ show_progress = manual progress_title = _("Sending customer statements...") if show_progress: publish_progress(percent=0, title=progress_title) if company is None: company = frappe.db.get_single_value('Customer Statements Sender', 'company') if not company: frappe.throw( _('Company field is required on Customer Statements Sender')) exit() email_list = get_recipient_list() idx = 0 total = len(email_list) for row in email_list: idx += 1 if row.email_id is not None: if row.send_statement == "Yes": if show_progress: publish_progress( percent=(idx / total * 100), title=progress_title, description=' Creating PDF for {0}'.format( row.customer)) data = get_report_content(company, row.customer) # Get PDF Data pdf_data = get_pdf(data) if not pdf_data: return attachments = [{ 'fname': get_file_name(), 'fcontent': pdf_data }] frappe.sendmail( recipients=row.email_id, subject='Customer Statement from {0}'.format(company), message= 'Good day. <br> Please find attached your latest statement from {0}' .format(company), attachments=attachments, reference_doctype="Report", reference_name="General Ledger") if show_progress: publish_progress(percent=100, title=progress_title) frappe.msgprint('Emails queued for sending')
def send_statements(company=None, manual=None): """ Send out customer statements """ show_progress = manual progress_title = _("Sending customer statements...") if show_progress: publish_progress(percent=0, title=progress_title) if company is None: company = frappe.db.get_single_value("Customer Statements Sender", "company") if not company: frappe.throw( _("Company field is required on Customer Statements Sender")) exit() from_date_for_all_customers = frappe.db.get_single_value( "Customer Statements Sender", "from_date_for_all_customers") to_date_for_all_customers = frappe.db.get_single_value( "Customer Statements Sender", "to_date_for_all_customers") email_list = get_recipient_list() idx = 0 total = len(email_list) for row in email_list: idx += 1 if row.email_id is not None and row.email_id != "": if row.send_statement == "Yes": if show_progress: publish_progress( percent=(idx / total * 100), title=progress_title, description=" Creating PDF for {0}".format( row.customer), ) send_individual_statement( row.customer, row.email_id, company, from_date_for_all_customers, to_date_for_all_customers, ) if show_progress: publish_progress(percent=100, title=progress_title) frappe.msgprint("Emails queued for sending")
def submit_salary_slips_for_employees_mod(payroll_entry, salary_slips, publish_progress=True): """ MODIFIED: Single line changed payroll_entry.make_accrual_jv_entry() -> payroll_entry.register_payroll_in_gl(cancel=False) """ submitted_ss = [] not_submitted_ss = [] frappe.flags.via_payroll_entry = True count = 0 for ss in salary_slips: ss_obj = frappe.get_doc("Salary Slip", ss[0]) if ss_obj.net_pay < 0: not_submitted_ss.append(ss[0]) else: try: ss_obj.submit() submitted_ss.append(ss_obj) except frappe.ValidationError: not_submitted_ss.append(ss[0]) count += 1 if publish_progress: frappe.publish_progress(count * 100 / len(salary_slips), title=_("Submitting Salary Slips...")) if submitted_ss: #payroll_entry.make_accrual_jv_entry() payroll_entry.register_payroll_in_gl(cancel=False) frappe.msgprint( _("Salary Slip submitted for period from {0} to {1}").format( ss_obj.start_date, ss_obj.end_date)) payroll_entry.email_salary_slip(submitted_ss) payroll_entry.db_set("salary_slips_submitted", 1) payroll_entry.notify_update() if not submitted_ss and not not_submitted_ss: frappe.msgprint( _("No salary slip found to submit for the above selected criteria OR salary slip already submitted" )) if not_submitted_ss: frappe.msgprint(_("Could not submit some Salary Slips")) ######################################################################### ### NOTHING TO SEE BELOW HERE; OLD STUFF KEPT FOR REVIEW IF NECESSARY ### ######################################################################### # def something_else(self): # payable_amounts = {} # total_payable = 0 # for slip in slips: # name = slip['salary_slip'] # emp = slip['employee'] # net_amount = frappe.db.get_value(doctype="Salary Slip", fieldname="rounded_total", filters={"name": name}) # payable_amounts[emp] = net_amount # total_payable += net_amount # self.set('base_grand_total', total_payable) # # register accounts that will be set against earnings and deductions # against_earnings = [] # for acct in deductions: # against_earnings.append(acct) # if payroll_account_is_type_payable: # for emp in payable_amounts: # against_earnings.append(emp) # else: # against_earnings.append(default_payroll_payable_account) # against_deductions = [] # for acct in earnings: # against_deductions.append(acct) # ### Now we'll build up the general ledger map # gl_map = [] # if self.aggregate_salary_slips: # if earnings or deductions: # for acc, amount in earnings.items(): # print(acc, amount) # gl_map.append(self.new_gl_line( # account=acc, # against=", ".join(list(set(against_earnings))), # debit=amount # )) # # deductions # for acc, amount in deductions.items(): # gl_map.append(self.new_gl_line( # account=acc, # against=", ".join(list(set(against_deductions))), # credit=amount, # )) # # Loan # for loan in loan_details: # gl_map.append(self.new_gl_line( # account=loan.loan_account, # against=loan.employee, # credit=loan.principal_amount, # party_type="Employee", # party=loan.employee # )) # if loan.interest_amount and not loan.interest_income_account: # frappe.throw(_("Select interest income account in employee loan {0}").format(loan.loan)) # if loan.interest_income_account and loan.interest_amount: # gl_map.append(self.new_gl_line( # account=loan.interest_income_account, # against=loan.employee, # credit=loan.interest_amount, # )) # gl_map.append(self.new_gl_line( # account=default_payroll_payable_account, # against=",".join(list(set(against_deductions))), # credit=total_payable # )) # else: # # if payroll_account_is_type_payable: # # #for emp, amt in payable_amounts.items(): # # # gl_map.append(self.new_gl_line( # # # account=default_payroll_payable_account, # # # against=", ".join(list(set(against_deductions))), # # # credit=amt, # # # party_type="Employee", # # # party=emp # # # )) # for slip in slips: # name = slip['salary_slip'] # #slip_doc = frappe.get_doc("Salary Slip", name) # emp = slip['employee'] # net_amount = frappe.db.get_value(doctype="Salary Slip", fieldname="rounded_total", filters={"name": name}) # payable_amounts[emp] = net_amount # total_payable += net_amount # print(slip) # gl_map.append(self.new_gl_line( # account=default_payroll_payable_account, # against=", ".join(list(set(against_deductions))), # credit=net_amount, # party_type="Employee", # party=emp, # against_voucher=name, # against_voucher_type="Salary Slip" # )) # # earnings and deductions # if earnings or deductions: # # earnings # for acc, amount in earnings.items(): # print(acc, amount) # gl_map.append(self.new_gl_line( # account=acc, # against=", ".join(list(set(against_earnings))), # debit=amount # )) # # deductions # for acc, amount in deductions.items(): # gl_map.append(self.new_gl_line( # account=acc, # against=", ".join(list(set(against_deductions))), # credit=amount, # )) # # Loan # ### # ##Get rid of this distinction; this entire section will now be for aggregated # ### # # payable # if payroll_account_is_type_payable: # for slip in slips: # name = slip['salary_slip'] # #slip_doc = frappe.get_doc("Salary Slip", name) # emp = slip['employee'] # net_amount = frappe.db.get_value(doctype="Salary Slip", fieldname="rounded_total", filters={"name": name}) # payable_amounts[emp] = net_amount # total_payable += net_amount # print(slip) # gl_map.append(self.new_gl_line( # account=default_payroll_payable_account, # against=", ".join(list(set(against_deductions))), # credit=net_amount, # party_type="Employee", # party=emp, # against_voucher=name, # against_voucher_type="Salary Slip" # )) # #for emp, amt in payable_amounts.items(): # # gl_map.append(self.new_gl_line( # # account=default_payroll_payable_account, # # against=", ".join(list(set(against_deductions))), # # credit=amt, # # party_type="Employee", # # party=emp # # )) # else: # gl_map.append(self.new_gl_line( # account=default_payroll_payable_account, # against=",".join(list(set(against_deductions))), # credit=total_payable # )) """ what follows are all functions used to fetch and aggregate various parts of Salary Slips, called by the register_payroll_in_gl function. """ # def get_salary_component_total(self, component_type = None): # salary_components = self.get_salary_components(component_type) # if salary_components: # component_dict = {} # for item in salary_components: # component_dict[item['salary_component']] = component_dict.get(item['salary_component'], 0) + item['amount'] # account_details = self.get_account(component_dict = component_dict) # return account_details # def get_salary_components(self, component_type): # salary_slips = self.get_sal_slip_list(as_dict = True) # if salary_slips: # salary_components = frappe.db.sql("""select salary_component, amount, parentfield # from `tabSalary Detail` where parentfield = '%s' and parent in (%s)""" % # (component_type, ', '.join(['%s']*len(salary_slips))), tuple([d.salary_slip for d in salary_slips]), as_dict=True) # return salary_components # def get_account(self, component_dict = None): # account_dict = {} # for s, a in component_dict.items(): # account = self.get_salary_component_account(s) # account_dict[account] = account_dict.get(account, 0) + a # return account_dict # def get_salary_component_account(self, salary_component): # account = frappe.db.get_value("Salary Component Account", # {"parent": salary_component, "company": self.company}, "default_account") # if not account: # frappe.throw(_("Please set default account in Salary Component {0}") # .format(salary_component)) # return account # def get_default_payroll_payable_account(self): # payroll_payable_account = frappe.db.get_value("Company", # {"company_name": self.company}, "default_payroll_payable_account") # if not payroll_payable_account: # frappe.throw(_("Please set Default Payroll Payable Account in Company {0}") # .format(self.company)) # return payroll_payable_account # def get_loan_details(self): # """ # Get loan details from submitted salary slip based on selected criteria # """ # cond = self.get_filter_condition() # return frappe.db.sql(""" select t1.employee, eld.loan_account, eld.loan, # eld.interest_income_account, eld.principal_amount, eld.interest_amount, eld.total_payment # from # `tabSalary Slip` t1, `tabSalary Slip Loan` eld # where # t1.docstatus = 1 and t1.name = eld.parent and start_date >= %s and end_date <= %s %s # """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) or [] # def get_total_salary_amount(self): # """ # Get total salary amount from submitted salary slip based on selected criteria # """ # cond = self.get_filter_condition() # totals = frappe.db.sql(""" select sum(rounded_total) as rounded_total from `tabSalary Slip` t1 # where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s # """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) # return totals and totals[0] or None # def update_salary_slip_status(self, jv_name = None): # ss_list = self.get_sal_slip_list(ss_status=1) # for ss in ss_list: # ss_obj = frappe.get_doc("Salary Slip",ss[0]) # frappe.db.set_value("Salary Slip", ss_obj.name, "status", "Paid") # frappe.db.set_value("Salary Slip", ss_obj.name, "journal_entry", jv_name) # def set_start_end_dates(self): # self.update(get_start_end_dates(self.payroll_frequency, # self.start_date or self.posting_date, self.company)) # @frappe.whitelist() # def get_start_end_dates(payroll_frequency, start_date=None, company=None): # '''Returns dict of start and end dates for given payroll frequency based on start_date''' # if payroll_frequency == "Monthly" or payroll_frequency == "Bimonthly" or payroll_frequency == "": # fiscal_year = get_fiscal_year(start_date, company=company)[0] # month = "%02d" % getdate(start_date).month # m = get_month_details(fiscal_year, month) # if payroll_frequency == "Bimonthly": # if getdate(start_date).day <= 15: # start_date = m['month_start_date'] # end_date = m['month_mid_end_date'] # else: # start_date = m['month_mid_start_date'] # end_date = m['month_end_date'] # else: # start_date = m['month_start_date'] # end_date = m['month_end_date'] # if payroll_frequency == "Weekly": # end_date = add_days(start_date, 6) # if payroll_frequency == "Fortnightly": # end_date = add_days(start_date, 13) # if payroll_frequency == "Daily": # end_date = start_date # return frappe._dict({ # 'start_date': start_date, 'end_date': end_date # }) # def validate_employee_attendance(self): # employees_to_mark_attendance = [] # days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0 # for employee_detail in self.employees: # days_holiday = self.get_count_holidays_of_employee(employee_detail.employee) # days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee) # days_in_payroll = date_diff(self.end_date, self.start_date) + 1 # if days_in_payroll > days_holiday + days_attendance_marked: # employees_to_mark_attendance.append({ # "employee": employee_detail.employee, # "employee_name": employee_detail.employee_name # }) # return employees_to_mark_attendance # def get_count_holidays_of_employee(self, employee): # holiday_list = get_holiday_list_for_employee(employee) # holidays = 0 # if holiday_list: # days = frappe.db.sql("""select count(*) from tabHoliday where # parent=%s and holiday_date between %s and %s""", (holiday_list, # self.start_date, self.end_date)) # if days and days[0][0]: # holidays = days[0][0] # return holidays # def get_count_employee_attendance(self, employee): # marked_days = 0 # attendances = frappe.db.sql("""select count(*) from tabAttendance where # employee=%s and docstatus=1 and attendance_date between %s and %s""", # (employee, self.start_date, self.end_date)) # if attendances and attendances[0][0]: # marked_days = attendances[0][0] # return marked_days # def get_frequency_kwargs(frequency_name): # frequency_dict = { # 'monthly': {'months': 1}, # 'fortnightly': {'days': 14}, # 'weekly': {'days': 7}, # 'daily': {'days': 1} # } # return frequency_dict.get(frequency_name) # @frappe.whitelist() # def get_end_date(start_date, frequency): # start_date = getdate(start_date) # frequency = frequency.lower() if frequency else 'monthly' # kwargs = get_frequency_kwargs(frequency) if frequency != 'bimonthly' else get_frequency_kwargs('monthly') # # weekly, fortnightly and daily intervals have fixed days so no problems # end_date = add_to_date(start_date, **kwargs) - relativedelta(days=1) # if frequency != 'bimonthly': # return dict(end_date=end_date.strftime(DATE_FORMAT)) # else: # return dict(end_date='') # def get_month_details(year, month): # ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") # if ysd: # import calendar, datetime # diff_mnt = cint(month)-cint(ysd.month) # if diff_mnt<0: # diff_mnt = 12-int(ysd.month)+cint(month) # msd = ysd + relativedelta(months=diff_mnt) # month start date # month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month # mid_start = datetime.date(msd.year, cint(month), 16) # month mid start date # mid_end = datetime.date(msd.year, cint(month), 15) # month mid end date # med = datetime.date(msd.year, cint(month), month_days) # month end date # return frappe._dict({ # 'year': msd.year, # 'month_start_date': msd, # 'month_end_date': med, # 'month_mid_start_date': mid_start, # 'month_mid_end_date': mid_end, # 'month_days': month_days # }) # else: # frappe.throw(_("Fiscal Year {0} not found").format(year)) # @frappe.whitelist() # def create_log(ss_list): # if not ss_list: # frappe.throw( # _("There are no employees for the listed criteria currently missing salary slips."), # title='Note' # ) # return ss_list # def create_submit_log(submitted_ss, unsubmitted_ss): # if not submitted_ss and not unsubmitted_ss: # frappe.msgprint(_("No salary slips found for the above criteria")) # if unsubmitted_ss: # frappe.msgprint(_("Could not submit a Salary Slip <br>\ # Possible reasons: <br>\ # 1. Net pay is less than 0. <br>\ # 2. Company Email Address specified in employee master is not valid. <br>")) # def format_as_links(salary_slip): # return ['<a href="#Form/Salary Slip/{0}">{0}</a>'.format(salary_slip)] # def get_salary_slip_list(name, docstatus, as_dict=0): # payroll_entry = frappe.get_doc('Payroll Entry', name) # salary_slip_list = frappe.db.sql( # "select t1.name, t1.salary_structure from `tabSalary Slip` t1 " # "where t1.docstatus = %s " # "and t1.start_date >= %s " # "and t1.end_date <= %s", # (docstatus, payroll_entry.start_date, payroll_entry.end_date), # as_dict=as_dict # ) # return salary_slip_list # @frappe.whitelist() # def payroll_entry_has_created_slips(name): # response = {} # draft_salary_slips = get_salary_slip_list(name, docstatus=0) # submitted_salary_slips = get_salary_slip_list(name, docstatus=1) # response['draft'] = 1 if draft_salary_slips else 0 # response['submitted'] = 1 if submitted_salary_slips else 0 # return response # def get_payroll_entry_bank_entries(payroll_entry_name): # journal_entries = frappe.db.sql( # 'select name from `tabJournal Entry Account` ' # 'where reference_type="Payroll Entry" ' # 'and reference_name=%s and docstatus=1', # payroll_entry_name, # as_dict=1 # ) # return journal_entries # @frappe.whitelist() # def payroll_entry_has_bank_entries(name): # response = {} # bank_entries = get_payroll_entry_bank_entries(name) # response['submitted'] = 1 if bank_entries else 0 # return response ###### CURRENTLY UNUSED METHODS FROM THE CORE CLASS # def on_submit(self): # # identical to payroll_entry # self.submit_salary_slips() # def before_submit(self): # # identical to payroll entry # if self.validate_attendance: # if self.validate_employee_attendance(): # frappe.throw(_("Cannot Submit, Employees left to mark attendance")) # def get_emp_list(self): # # identical to payroll_entry method # """ # Returns list of active employees based on selected criteria # and for which salary structure exists # """ # cond = self.get_filter_condition() # cond += self.get_joining_releiving_condition() # condition = '' # if self.payroll_frequency: # condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency} # sal_struct = frappe.db.sql_list(""" # select # name from `tabSalary Structure` # where # docstatus = 1 and # is_active = 'Yes' # and company = %(company)s and # ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s # {condition}""".format(condition=condition), # {"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) # if sal_struct: # cond += "and t2.salary_structure IN %(sal_struct)s " # cond += "and %(from_date)s >= t2.from_date" # emp_list = frappe.db.sql(""" # select # distinct t1.name as employee, t1.employee_name, t1.department, t1.designation # from # `tabEmployee` t1, `tabSalary Structure Assignment` t2 # where # t1.name = t2.employee # and t2.docstatus = 1 # %s order by t2.from_date desc # """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date}, as_dict=True) # return emp_list # def fill_employee_details(self): # # this method now unused and replaced with a fill_salary_slips # self.set('employees', []) # employees = self.get_emp_list() # if not employees: # frappe.throw(_("No employees for the mentioned criteria")) # for d in employees: # self.append('employees', d) # self.number_of_employees = len(employees) # if self.validate_attendance: # return self.validate_employee_attendance() # def get_joining_releiving_condition(self): # # identical to payroll_entry method # cond = """ # and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' # and ifnull(t1.relieving_date, '2199-12-31') >= '%(start_date)s' # """ % {"start_date": self.start_date, "end_date": self.end_date} # return cond # def get_filter_condition(self): # """ # Assemble sql clause matching filters specified in the document # """ # for fieldname in ['company', 'start_date', 'end_date']: # if not self.get(fieldname): # frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname))) # cond = '' # for f in ['company', 'branch', 'department', 'designation']: # if self.get(f): # cond += " and t1." + f + " = '" + self.get(f).replace("'", "\'") + "'" # return cond # def get_filter_condition(self): # # identical to payment_entry # self.check_mandatory() # cond = '' # for f in ['company', 'branch', 'department', 'designation']: # if self.get(f): # cond += " and t1." + f + " = '" + self.get(f).replace("'", "\'") + "'" # return cond # def submit_salary_slips(self): # """ # Submit all salary slips listed in the Payroll Salary Slip Details table. This is typically done when the # Payroll Voucher document is submitted. # TODO: this could probably be simplified to simply iterate through the Payroll Salary Slip Details table # """ # self.check_permission('write') # ss_list = self.get_sal_slip_list() # submitted_ss = [] # unsubmitted_ss = [] # for ss in ss_list: # ss_obj = frappe.get_doc("Salary Slip",ss[0]) # ss_dict = {} # ss_dict["Employee Name"] = ss_obj.employee_name # ss_dict["Total Pay"] = fmt_money(ss_obj.net_pay, # currency = frappe.defaults.get_global_default("currency")) # ss_dict["Salary Slip"] = format_as_links(ss_obj.name)[0] # if ss_obj.net_pay<0: # unsubmitted_ss.append(ss_dict) # else: # try: # ss_obj.submit() # submitted_ss.append(ss_obj) # except frappe.ValidationError: # unsubmitted_ss.append(ss_dict) # if submitted_ss: # self.register_payroll_in_gl() # frappe.msgprint(_("Salary Slips submitted for period from {0} to {1}").format(ss_obj.start_date, ss_obj.end_date)) # self.email_salary_slip(submitted_ss) # return create_submit_log(submitted_ss, unsubmitted_ss) # def email_salary_slip(self, submitted_ss): # """ # send emails to employees if indicated by HR Settings # """ # if frappe.db.get_single_value("HR Settings", "email_salary_slip_to_employee"): # for ss in submitted_ss: # ss.email_salary_slip()
def upload_calendar(data): #Check if called from client side (not necessary) if (isinstance(data, str)): data = json.loads(data) #Connect to CalDav Account account = frappe.get_doc("CalDav Account", data["caldavaccount"]) """ account = data["caldavaccount"] """ client = caldav.DAVClient(url=account.url, username=account.username, password=account.password) principal = client.principal() calendars = principal.calendars() cal = None #Look for the right calendar for calendar in calendars: if (str(calendar) == data["calendarurl"]): cal = calendar #Go through events erp_events = frappe.db.sql(""" SELECT * FROM `tabEvent` WHERE icalendar = '{icalendar}' AND custom_pattern is NULL; """.format(icalendar=data["icalendar"]), as_dict=1) weekdays = [ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday" ] rrweekdays = [RR.MO, RR.TU, RR.WE, RR.TH, RR.FR, RR.SA, RR.SU] upstats = { "10a": 0, "10b": 0, "10c": 0, "11b": 0, "not_uploadable": 0, "cancelled_or_closed_of_no_uploadable": 0, "exceptions": 0, } rstats = { "mapped": 0, "not_mapped": 0, } #Error Stack error_stack = [] for idx, ee in enumerate(erp_events): uploadable = True try: print(ee) #Case 10a: Status Open, everything nominal if (ee["status"] == "Open"): new_calendar = vobject.newFromBehavior('vcalendar') e = new_calendar.add('vevent') e.add('summary').value = ee["subject"] dtstart = ee["starts_on"] e.add('dtstart').value = dtstart e.add('description').value = ee["description"] if (ee["event_type"] in ["Public", "Private", "Confidential"]): e.add('class').value = ee["event_type"] #Case 10b: Status Open, but Event Type is Cancelled elif (ee["event_type"] == "Cancelled"): uploadable = False upstats["10b"] += 1 #Case 10c: Status Open, but Event Type not in [Public, Private, Confidential,Cancelled] else: uploadable = False upstats["10c"] += 1 raise Exception( 'Exception:', 'Event with Name ' + ee["name"] + ' has the invalid Event Type ' + ee["event_type"]) dtend = ee["ends_on"] if (dtend == None): dtend = dtstart + datetime.timedelta(minutes=15) frappe.db.set_value('Event', ee["name"], 'ends_on', dtend, update_modified=False) if (ee["all_day"] == 0): e.add('dtend').value = dtend else: e.dtstart.value = dtstart.date() dtend = (dtend.date() + datetime.timedelta(days=1)) e.add('dtend').value = dtend if (ee["last_modified"] == None): frappe.db.set_value('Event', ee["name"], 'last_modified', ee["modified"].replace(microsecond=0), update_modified=False) e.add('last-modified').value = ee["modified"].replace( microsecond=0) else: e.add('last-modified').value = ee["last_modified"] if (ee["created_on"] == None): frappe.db.set_value('Event', ee["name"], 'created_on', ee["creation"].replace(microsecond=0), update_modified=False) e.add('created').value = ee["creation"].replace( microsecond=0) else: e.add('created').value = ee["created_on"] #Create rrule rrule = None until = ee["repeat_till"] byweekday = [] if (ee["repeat_this_event"] == 1 and ee["repeat_till"] != None): until = datetime.datetime(until.year, until.month, until.day, dtstart.hour, dtstart.minute, dtstart.second) if (ee["repeat_on"] == "Daily"): rrule = RR.rrule(freq=RR.DAILY, until=until) elif (ee["repeat_on"] == "Weekly"): for idx, weekday in enumerate(weekdays): if (ee[weekday] == 1): byweekday.append(rrweekdays[idx]) rrule = RR.rrule(freq=RR.WEEKLY, until=until, byweekday=byweekday) elif (ee["repeat_on"] == "Monthly"): rrule = RR.rrule(freq=RR.MONTHLY, until=until) elif (ee["repeat_on"] == "Yearly"): rrule = RR.rrule(freq=RR.YEARLY, until=until) if (rrule != None): e.add('rrule').value = rrule rstats["mapped"] += 1 else: rstats["not_mapped"] += 1 #Remove None Children none_attributes = [] for child in e.getChildren(): if (child.value == None): none_attributes.append(child.name.lower()) for attr in none_attributes: e.__delattr__(attr) ics = new_calendar.serialize() print(ics) frappe.db.set_value('Event', ee["name"], 'uid', e.uid.value, update_modified=False) #Upload if (uploadable): cal.save_event(ics) upstats["10a"] += 1 else: upstats["not_uploadable"] += 1 #Case 11a: Status != Open else: uploadable = False upstats["11b"] += 1 except Exception as ex: #traceback.print_exc() tb = traceback.format_exc() upstats["exceptions"] += 1 error_stack.append({ "message": "Could not upload event. Exception: \n" + tb, "event": json.dumps(ee) }) #Update UI percent_progress = idx / len(erp_events) * 100 frappe.publish_progress(percent=percent_progress, title="Uploading") #Return JSON and Log message = {} upstats["cancelled_or_closed_of_no_uploadable"] = upstats[ "not_uploadable"] - upstats["10b"] - upstats["11b"] message["upstats"] = upstats message["rstats"] = rstats message["error_stack"] = error_stack """ """ d = frappe.get_doc("iCalendar", data["icalendar"]) d.last_sync_log = json.dumps(message) d.save() d.add_comment('Comment', text="Stats:\n" + str(upstats) + "\nRRule Stats:\n" + str(rstats)) """ """ return json.dumps(message)
def download_calendar(data): #UI Progress Display percent_progress = 0 frappe.show_progress('Downloading..', percent_progress, 100, 'Please wait') #Constants sync_period = 90 #Check if called from client side (not necessary) if (isinstance(data, str)): data = json.loads(data) #Connect to CalDav Account account = frappe.get_doc("CalDav Account", data["caldavaccount"]) """ account = data["caldavaccount"] """ client = caldav.DAVClient(url=account.url, username=account.username, password=account.password) principal = client.principal() calendars = principal.calendars() cal = None doc = None #Look for the right calendar for calendar in calendars: if (str(calendar) == data["calendarurl"]): cal = calendar #Go through Events events = cal.events() #Stats stats = { "1a": 0, "1b": 0, "1c": 0, "2a": 0, "3a": 0, "3b": 0, "4a": 0, "else": 0, "error": 0, "not_inserted": 0, "exception_block_standard": 0, "exception_block_meta": 0 } rstats = { "norrule": 0, "daily": 0, "weekly": 0, "monthly": 0, "yearly": 0, "finite": 0, "infinite": 0, "total": len(events), "singular_event": 0, "error": 0, "exception": 0 } #Error Stack error_stack = [] for idx, event in enumerate(events): vev = event.vobject_instance.vevent #By default an event is not insertable in ERP insertable = False #Handle standard fields try: #Following conversion makes it Timezone naive! if (hasattr(vev, "dtstart") and type(vev.dtstart.value) is datetime.date): value = vev.dtstart.value dtstart = datetime.datetime(value.year, value.month, value.day) elif (hasattr(vev, "dtstart")): dtstart = vev.dtstart.value else: dtstart = None if (hasattr(vev, "dtend") and type(vev.dtend.value) is datetime.date): value = vev.dtend.value dtend = datetime.datetime(value.year, value.month, value.day) elif (hasattr(vev, "dtend")): dtend = vev.dtend.value else: dtend = None #Berechne Dinge if (hasattr(vev, "dtstart") and hasattr(vev, "dtend")): timedelta = dtend - dtstart days = (dtend.date() - dtstart.date()).days elif (hasattr(vev, "dtstart") and hasattr(vev, "duration")): timedelta = vev.duration.value #Real time difference days = ( (dtstart + vev.duration.value).date() - dtstart.date()).days #Full days between the dates 0,1,2... #Standard Fields doc = frappe.new_doc("Event") """ doc = DotMap() """ if (vev.summary.value == "WG"): vev.prettyPrint() doc.subject = vev.summary.value doc.starts_on = dtstart.strftime("%Y-%m-%d %H:%M:%S") if (hasattr(vev, "description")): doc.description = vev.description.value doc.event_type = vev.__getattr__("class").value.title() #Case 1a: has dtend, within a day days = None timedelta = None if ((hasattr(vev, "dtend") and days == 0)): doc.ends_on = dtend.strftime("%Y-%m-%d %H:%M:%S") insertable = True stats["1a"] += 1 #Case 1b: has duration, within a day elif (hasattr(vev, "duration") and days == 0): doc.ends_on = ( dtstart + vev.duration.value).strftime("%Y-%m-%d %H:%M:%S") insertable = True stats["1b"] += 1 #Case 1c: Allday, one day elif (timedelta.days == 1 and timedelta.seconds == 0 and dtstart.hour == 0 and dtstart.minute == 0): doc.ends_on = "" doc.all_day = 1 insertable = True stats["1c"] += 1 #Case 2a: Allday, more than one day elif (timedelta.days >= 1 and timedelta.seconds == 0): doc.ends_on = (dtstart + timedelta).strftime("%Y-%m-%d %H:%M:%S") doc.all_day = 1 insertable = True stats["2a"] += 1 #Case 3a: has dtend, not within a day elif ((hasattr(vev, "dtend") and days >= 1)): doc.ends_on = (dtstart + timedelta).strftime("%Y-%m-%d %H:%M:%S") insertable = True stats["3a"] += 1 #Case 3b: has duration, not within a day elif ((hasattr(vev, "duration") and days > 0)): doc.ends_on = (dtstart + timedelta).strftime("%Y-%m-%d %H:%M:%S") insertable = True stats["3b"] += 1 #Case else: ( ATM: No dtend, No Duration,...) else: stats["else"] += 1 except Exception as ex: #traceback.print_exc() tb = traceback.format_exc() insertable = False stats["exception_block_standard"] += 1 error_stack.append({ "message": "Problem with Standard Fields/Cases. Exception: \n" + tb, "icalendar": vev.serialize() }) #If the event has a recurrence rule this will be handled here, by default rrules are not mappable to ERP mapped = False try: #RRULE CONVERSION if (hasattr(vev, "rrule")): rule = dateutil.rrule.rrulestr(vev.rrule.value, dtstart=vev.dtstart.value) #Include only mappable rrules if (isMappable(vev, rule)): #DAILY if (rule._freq == 3 and noByDay(vev.rrule.value)): doc.repeat_this_event = 1 doc.repeat_on = "Daily" until = getUntil(vev.dtstart.value, rule) if until: doc.repeat_till = until.strftime("%Y-%m-%d") mapped = True rstats["daily"] += 1 #DAILY to WEEKLY (Special Case SP1) elif (rule._freq == 3 and not noByDay(vev.rrule.value)): match = re.search( r'BY[A-Z]{4,5}DAY', vev.rrule.value ) #Catches BYWEEKDAY, BYMONTHDAY and BYYEARDAY if match: print("Special Case not applicable") rstats["error"] += 1 error_stack.append({ "message": "Daily SP1 not applicable", "icalendar": vev.serialize() }) else: doc.repeat_this_event = 1 doc.repeat_on = "Weekly" until = getUntil(vev.dtstart.value, rule) if until: doc.repeat_till = until.strftime("%Y-%m-%d") if 0 in rule._byweekday: doc.monday = 1 if 1 in rule._byweekday: doc.tuesday = 1 if 2 in rule._byweekday: doc.wednesday = 1 if 3 in rule._byweekday: doc.thursday = 1 if 4 in rule._byweekday: doc.friday = 1 if 5 in rule._byweekday: doc.saturday = 1 if 6 in rule._byweekday: doc.sunday = 1 mapped = True rstats["weekly"] += 1 #WEEKLY elif (rule._freq == 2): doc.repeat_this_event = 1 doc.repeat_on = "Weekly" until = getUntil(vev.dtstart.value, rule) if until: doc.repeat_till = until.strftime("%Y-%m-%d") if 0 in rule._byweekday: doc.monday = 1 if 1 in rule._byweekday: doc.tuesday = 1 if 2 in rule._byweekday: doc.wednesday = 1 if 3 in rule._byweekday: doc.thursday = 1 if 4 in rule._byweekday: doc.friday = 1 if 5 in rule._byweekday: doc.saturday = 1 if 6 in rule._byweekday: doc.sunday = 1 mapped = True rstats["weekly"] += 1 #MONTHLY elif (rule._freq == 1 and noByDay(vev.rrule.value) and isNotFebruaryException(vev.dtstart.value.date())): doc.repeat_this_event = 1 doc.repeat_on = "Monthly" until = getUntil(vev.dtstart.value, rule) if until: doc.repeat_till = until.strftime("%Y-%m-%d") mapped = True rstats["monthly"] += 1 #YEARLY elif (rule._freq == 0 and noByDay(vev.rrule.value) and isNotFebruaryException(vev.dtstart.value.date())): doc.repeat_this_event = 1 doc.repeat_on = "Yearly" until = getUntil(vev.dtstart.value, rule) if until: doc.repeat_till = until.strftime("%Y-%m-%d") mapped = True rstats["yearly"] += 1 #Not mapped else: rstats["error"] += 1 error_stack.append({ "message": "Mappable but Not mapped", "icalendar": vev.serialize() }) else: mapped = True rstats["norrule"] += 1 except Exception as ex: #traceback.print_exc() tb = traceback.format_exc() mapped = False rstats["exception"] += 1 error_stack.append({ "message": "RRule mapping error. Exception: \n" + tb, "icalendar": vev.serialize() }) #Handle meta fields and insert doc into erp try: #Specials: Metafields if (hasattr(vev, "transp")): if (vev.transp.value == "TRANSPARENT"): doc.color = color_variant(data["color"]) elif (vev.transp.value == "OPAQUE"): doc.color = data["color"] else: doc.color = data["color"] if (hasattr(vev, "status")): #print("Status: " + vev.status.value) pass if (hasattr(vev, "organizer")): #print("Organizer: " + vev.organizer.value) pass if (hasattr(vev, "attendee")): #vev.prettyPrint() pass if (hasattr(vev, "sequence")): #print("Sequence: " + vev.sequence.value) pass if (hasattr(vev, "location")): #print("Location: " + vev.location.value) pass #ICalendar Meta Information for Sync if (hasattr(vev, "last_modified")): doc.last_modified = vev.last_modified.value.strftime( "%Y-%m-%d %H:%M:%S") if (hasattr(vev, "created")): doc.created_on = vev.created.value.strftime( "%Y-%m-%d %H:%M:%S") if (hasattr(vev, "uid")): doc.uid = vev.uid.value else: raise Exception('Exception:', 'Event has no UID') doc.icalendar = data["icalendar"] #Insert if (insertable and mapped): """ """ doc.insert( ignore_permissions= False, # ignore write permissions during insert ignore_links=True, # ignore Link validation in the document ignore_if_duplicate= True, # dont insert if DuplicateEntryError is thrown ignore_mandatory= False # insert even if mandatory fields are not set ) #Has rrule, not mappable to Custom Pattern elif (hasattr(vev, "rrule")): #Finite Event is_finite = False if (re.search(r'UNTIL|COUNT', vev.rrule.value)): #vev.prettyPrint() datetimes = list(vev.getrruleset()) is_finite = True rstats["finite"] += 1 #Infinite Event else: #vev.prettyPrint() vev.rrule.value = vev.rrule.value + ";UNTIL=" + ( datetime.datetime.now() + datetime.timedelta( days=sync_period)).strftime("%Y%m%d") datetimes = list(vev.getrruleset()) rstats["infinite"] += 1 #Does not need a Custom Pattern if (len(datetimes) == 1): """ """ doc.insert( ) #TODO Remeber this is a strange case too, cause it has a rrule but only one event rstats["singular_event"] += 1 #Create Custom Pattern and Linked Events else: """ cp = DotMap() """ cp = frappe.new_doc("Custom Pattern") cp.title = vev.summary.value cp.icalendar = data["icalendar"] if (hasattr(vev, "created")): cp.created_on = vev.created.value.strftime( "%Y-%m-%d %H:%M:%S") if (hasattr(vev, "last_modified")): cp.last_modified = vev.last_modified.value.strftime( "%Y-%m-%d %H:%M:%S") if (hasattr(vev, "uid")): cp.uid = vev.uid.value else: raise Exception("Exception:", "Event has no UID") if (is_finite): cp.duration = "Finite" else: cp.duration = "Infinite" cp.newest_event_on = datetimes[ len(datetimes) - 1].strftime("%Y-%m-%d %H:%M:%S") """ """ cp.insert() for dt_starts_on in datetimes: """ event = DotMap() """ event = frappe.new_doc("Event") event.subject = vev.summary.value event.starts_on = dt_starts_on.strftime( "%Y-%m-%d %H:%M:%S") #In ERP Allday Events should have an empty field for ends_on -> dtendtime = None if (doc.ends_on == "" or doc.ends_on is None): event.ends_on = "" else: dtendtime = datetime.datetime.strptime( doc.ends_on, "%Y-%m-%d %H:%M:%S").time() event.ends_on = dt_starts_on.strftime( "%Y-%m-%d") + " " + dtendtime.strftime( "%H:%M:%S") event.type = vev.__getattr__("class").value.title() if (hasattr(vev, "transp")): if (vev.transp.value == "TRANSPARENT"): event.color = color_variant(data["color"]) elif (vev.transp.value == "OPAQUE"): event.color = data["color"] else: event.color = data["color"] if (hasattr(vev, "description")): event.description = vev.description.value else: event.description = None event.repeat_this_event = 1 """ """ event.custom_pattern = cp.name event.insert() cp.append('events', {'event': event.name}) cp.save() """ """ #print(datetimes) #print("CP created.") #Has no rrule, not mappable and strange for unknown reason else: stats["not_inserted"] += 1 except Exception as ex: #traceback.print_exc() tb = traceback.format_exc() stats["exception_block_meta"] += 1 error_stack.append({ "message": "Problem with Meta fields or doc insertion. Exception: \n" + tb, "icalendar": vev.serialize() }) #Update UI percent_progress = idx / len(events) * 100 frappe.publish_progress(percent=percent_progress, title="Downloading") #Return JSON and Log message = {} message["stats"] = stats message["rstats"] = rstats message["error_stack"] = error_stack """ """ d = frappe.get_doc("iCalendar", data["icalendar"]) d.last_sync_log = json.dumps(message) d.save() d.add_comment('Comment', text="Stats:\n" + str(stats) + "\nRRule Stats:\n" + str(rstats)) """ """ return json.dumps(message)