def make_raw_material_request(items, company, sales_order, project=None): if not frappe.has_permission("Sales Order", "write"): frappe.throw(_("Not permitted"), frappe.PermissionError) if isinstance(items, str): items = frappe._dict(json.loads(items)) for item in items.get("items"): item["include_exploded_items"] = items.get("include_exploded_items") item["ignore_existing_ordered_qty"] = items.get("ignore_existing_ordered_qty") item["include_raw_materials_from_sales_order"] = items.get( "include_raw_materials_from_sales_order" ) items.update({"company": company, "sales_order": sales_order}) raw_materials = get_items_for_material_requests(items) if not raw_materials: frappe.msgprint( _("Material Request not created, as quantity for Raw Materials already available.") ) return material_request = frappe.new_doc("Material Request") material_request.update( dict( doctype="Material Request", transaction_date=nowdate(), company=company, material_request_type="Purchase", ) ) for item in raw_materials: item_doc = frappe.get_cached_doc("Item", item.get("item_code")) schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) row = material_request.append( "items", { "item_code": item.get("item_code"), "qty": item.get("quantity"), "schedule_date": schedule_date, "warehouse": item.get("warehouse"), "sales_order": sales_order, "project": project, }, ) if not (strip_html(item.get("description")) and strip_html(item_doc.description)): row.description = item_doc.item_name or item.get("item_code") material_request.insert() material_request.flags.ignore_permissions = 1 material_request.run_method("set_missing_values") material_request.submit() return material_request
def make_raw_material_request(items, company, sales_order, project=None): if not frappe.has_permission("Sales Order", "write"): frappe.throw(_("Not permitted"), frappe.PermissionError) if isinstance(items, string_types): items = frappe._dict(json.loads(items)) for item in items.get('items'): item["include_exploded_items"] = items.get('include_exploded_items') item["ignore_existing_ordered_qty"] = items.get( 'ignore_existing_ordered_qty') item["include_raw_materials_from_sales_order"] = items.get( 'include_raw_materials_from_sales_order') items.update({'company': company, 'sales_order': sales_order}) raw_materials = get_items_for_material_requests(items) if not raw_materials: frappe.msgprint( _("Material Request not created, as quantity for Raw Materials already available." )) return material_request = frappe.new_doc('Material Request') material_request.update( dict(doctype='Material Request', transaction_date=nowdate(), company=company, requested_by=frappe.session.user, material_request_type='Purchase')) for item in raw_materials: item_doc = frappe.get_cached_doc('Item', item.get('item_code')) schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) row = material_request.append( 'items', { 'item_code': item.get('item_code'), 'qty': item.get('quantity'), 'schedule_date': schedule_date, 'warehouse': item.get('warehouse'), 'sales_order': sales_order, 'project': project }) if not (strip_html(item.get("description")) and strip_html(item_doc.description)): row.description = item_doc.item_name or item.get('item_code') material_request.insert() material_request.flags.ignore_permissions = 1 material_request.run_method("set_missing_values") material_request.submit() return material_request
def send_notification(self, doc): '''Build recipients and send Notification''' context = get_context(doc) context = {"doc": doc, "alert": self, "comments": None, "frappe": frappe} if doc.get("_comments"): context["comments"] = json.loads(doc.get("_comments")) if self.is_standard: self.load_standard_properties(context) if self.channel == 'Email': self.send_an_email(doc, context) elif self.channel == 'Slack': self.send_a_slack_msg(doc, context) elif self.channel == 'FCM': send_via_fcm(self, doc, context) elif self.channel == "SMS": recipients = get_sms_recipients_for_notification( notification=self, doc=doc, context=context) if recipients: send_sms(receiver_list=recipients, msg=strip_html( frappe.render_template(self.message, context)), provider=self.sms_providers) if self.set_property_after_alert: frappe.db.set_value(doc.doctype, doc.name, self.set_property_after_alert, self.property_value, update_modified=False) doc.set(self.set_property_after_alert, self.property_value)
def validate(self): if self.reference_doctype and self.reference_name: if not self.reference_owner: self.reference_owner = frappe.db.get_value(self.reference_doctype, self.reference_name, "owner") # prevent communication against a child table if frappe.get_meta(self.reference_doctype).istable: frappe.throw( _("Cannot create a {0} against a child document: {1}").format( _(self.communication_type), _(self.reference_doctype) ) ) if not self.user: self.user = frappe.session.user if not self.subject: self.subject = strip_html((self.content or "")[:141]) if not self.sent_or_received: self.sent_or_received = "Sent" self.set_status() self.set_sender_full_name() validate_email(self) validate_comment(self) self.set_timeline_doc()
def validate(self): if self.reference_doctype and self.reference_name: if not self.reference_owner: self.reference_owner = frappe.db.get_value( self.reference_doctype, self.reference_name, "owner") # prevent communication against a child table if frappe.get_meta(self.reference_doctype).istable: frappe.throw( _("Cannot create a {0} against a child document: {1}" ).format(_(self.communication_type), _(self.reference_doctype))) if not self.user: self.user = frappe.session.user if not self.subject: self.subject = strip_html((self.content or "")[:141]) if not self.sent_or_received: self.sent_or_received = "Sent" self.set_status() self.set_sender_full_name() validate_email(self) self.set_timeline_doc()
def _get_missing_mandatory_fields(self): """Get mandatory fields that do not have any values""" def get_msg(df): if df.fieldtype == "Table": return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label)) elif self.parentfield: return "{}: {} {} #{}: {}: {}".format( _("Error"), frappe.bold(_(self.doctype)), _("Row"), self.idx, _("Value missing for"), _(df.label)) else: return _("Error: Value missing for {0}: {1}").format( _(df.parent), _(df.label)) missing = [] for df in self.meta.get("fields", {"reqd": ('=', 1)}): if self.get(df.fieldname) in (None, []) or not strip_html( cstr(self.get(df.fieldname))).strip(): missing.append((df.fieldname, get_msg(df))) # check for missing parent and parenttype if self.meta.istable: for fieldname in ("parent", "parenttype"): if not self.get(fieldname): missing.append( (fieldname, get_msg(frappe._dict(label=fieldname)))) return missing
def _get_missing_mandatory_fields(self): """Get mandatory fields that do not have any values""" def get_msg(df): if df.fieldtype == "Table": return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label)) elif self.parentfield: return "{}: {} {} #{}: {}: {}".format(_("Error"), frappe.bold(_(self.doctype)), _("Row"), self.idx, _("Value missing for"), _(df.label)) else: return _("Error: Value missing for {0}: {1}").format(_(df.parent), _(df.label)) missing = [] for df in self.meta.get("fields", {"reqd": ('=', 1)}): if self.get(df.fieldname) in (None, []) or not strip_html(cstr(self.get(df.fieldname))).strip(): missing.append((df.fieldname, get_msg(df))) # check for missing parent and parenttype if self.meta.istable: for fieldname in ("parent", "parenttype"): if not self.get(fieldname): missing.append((fieldname, get_msg(frappe._dict(label=fieldname)))) return missing
def set_title_and_header(self, context): """Extract and set title and header from content or context.""" if not "no_header" in context: if "<!-- no-header -->" in context.main_section: context.no_header = 1 if "<!-- title:" in context.main_section: context.title = re.findall('<!-- title:([^>]*) -->', context.main_section)[0].strip() if context.get("page_titles") and context.page_titles.get(context.pathname): context.title = context.page_titles.get(context.pathname)[0] # header if context.no_header and "header" in context: context.header = "" if not context.no_header: # if header not set and no h1 tag in the body, set header as title if not context.header and "<h1" not in context.main_section: context.header = context.title # add h1 tag to header if context.get("header") and not re.findall("<h.>", context.header): context.header = "<h1>" + context.header + "</h1>" # if title not set, set title from header if not context.title and context.header: context.title = strip_html(context.header)
def set_title_and_header(out, context): """Extract and set title and header from content or context.""" out["no_header"] = context.get( "no_header", 0) or ("<!-- no-header -->" in out.get("content", "")) if "<!-- title:" in out.get("content", ""): out["title"] = re.findall('<!-- title:([^>]*) -->', out.get("content"))[0].strip() if "title" not in out: out["title"] = context.get("title") if context.get("page_titles") and context.page_titles.get( context.pathname): out["title"] = context.page_titles.get(context.pathname)[0] # header if out["no_header"]: out["header"] = "" else: if "title" not in out and out.get("header"): out["title"] = out["header"] if not out.get("header") and "<h1" not in out.get("content", ""): if out.get("title"): out["header"] = out["title"] if out.get("header") and not re.findall("<h.>", out["header"]): out["header"] = "<h1>" + out["header"] + "</h1>" if not out.get("header"): out["no_header"] = 1 out["title"] = strip_html(out.get("title") or "")
def set_title_and_header(out, context): """Extract and set title and header from content or context.""" out["no_header"] = context.get("no_header", 0) or ("<!-- no-header -->" in out.get("content", "")) if "<!-- title:" in out.get("content", ""): out["title"] = re.findall('<!-- title:([^>]*) -->', out.get("content"))[0].strip() if "title" not in out: out["title"] = context.get("title") if context.get("page_titles") and context.page_titles.get(context.pathname): out["title"] = context.page_titles.get(context.pathname)[0] # header if out["no_header"]: out["header"] = "" else: if "title" not in out and out.get("header"): out["title"] = out["header"] if not out.get("header") and "<h1" not in out.get("content", ""): if out.get("title"): out["header"] = out["title"] if out.get("header") and not re.findall("<h.>", out["header"]): out["header"] = "<h1>" + out["header"] + "</h1>" if not out.get("header"): out["no_header"] = 1 out["title"] = strip_html(out.get("title") or "")
def _get_missing_mandatory_fields(self): """Get mandatory fields that do not have any values""" def get_msg(df): if df.fieldtype == "Table": return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label)) elif self.parentfield: return "{}: {} #{}: {}: {}".format(_("Error"), _("Row"), self.idx, _("Value missing for"), _(df.label)) else: return "{}: {}: {}".format(_("Error"), _("Value missing for"), _(df.label)) missing = [] for df in self.meta.get("fields", {"reqd": 1}): if self.get(df.fieldname) in (None, []) or not strip_html( cstr(self.get(df.fieldname))).strip(): missing.append((df.fieldname, get_msg(df))) return missing
def set_title_and_header(self, context): """Extract and set title and header from content or context.""" if not "no_header" in context: if "<!-- no-header -->" in context.main_section: context.no_header = 1 if not context.title: context.title = extract_title(context.main_section, context.path_name) # header if context.no_header and "header" in context: context.header = "" if not context.no_header: # if header not set and no h1 tag in the body, set header as title if not context.header and "<h1" not in context.main_section: context.header = context.title # add h1 tag to header if context.get("header") and not re.findall( "<h.>", context.header): context.header = "<h1>" + context.header + "</h1>" # if title not set, set title from header if not context.title and context.header: context.title = strip_html(context.header)
def send_notification_email(doc): if doc.type == 'Energy Point' and doc.email_content is None: return from frappe.utils import get_url_to_form, strip_html doc_link = get_url_to_form(doc.document_type, doc.document_name) header = get_email_header(doc) email_subject = strip_html(doc.subject) frappe.sendmail( recipients = doc.for_user, subject = email_subject, template = "new_notification", args = { 'body_content': doc.subject, 'description': doc.email_content, 'document_type': doc.document_type, 'document_name': doc.document_name, 'doc_link': doc_link }, header = [header, 'orange'], now=frappe.flags.in_test )
def has_value(df, doc): value = doc.get(df.fieldname) if value in (None, ""): return False elif isinstance(value, basestring) and not strip_html(value).strip(): return False return True
def has_value(df, doc): value = doc.get(df.fieldname) if value in (None, ""): return False elif isinstance(value, string_types) and not strip_html(value).strip(): return False elif isinstance(value, list) and not len(value): return False return True
def set_metatags(self, context): context.metatags = { "name": context.title, "description": context.description or strip_html( (context.main_section or "").replace("\n", " "))[:500] } image = find_first_image(context.main_section or "") if image: context.metatags["image"] = image
def render_blocks(context): """returns a dict of block name and its rendered content""" out = {} env = frappe.get_jenv() def _render_blocks(template_path): source = frappe.local.jloader.get_source(frappe.local.jenv, template_path)[0] for referenced_template_path in meta.find_referenced_templates(env.parse(source)): if referenced_template_path: _render_blocks(referenced_template_path) template = frappe.get_template(template_path) for block, render in template.blocks.items(): out[block] = scrub_relative_urls(concat(render(template.new_context(context)))) _render_blocks(context["template_path"]) # default blocks if not found if "title" not in out and out.get("header"): out["title"] = out["header"] if "title" not in out: out["title"] = context.get("title") if "header" not in out and out.get("title"): out["header"] = out["title"] if not out["header"].startswith("<h"): out["header"] = "<h2>" + out["header"] + "</h2>" if "breadcrumbs" not in out: out["breadcrumbs"] = scrub_relative_urls( frappe.get_template("templates/includes/breadcrumbs.html").render(context)) if "<!-- no-sidebar -->" in out.get("content", ""): out["no_sidebar"] = 1 if "sidebar" not in out and not out.get("no_sidebar"): out["sidebar"] = scrub_relative_urls( frappe.get_template("templates/includes/sidebar.html").render(context)) out["title"] = strip_html(out.get("title") or "") # remove style and script tags from blocks out["style"] = re.sub("</?style[^<>]*>", "", out.get("style") or "") out["script"] = re.sub("</?script[^<>]*>", "", out.get("script") or "") return out
def has_value(df, doc): value = doc.get(df.fieldname) if value in (None, ""): return False elif isinstance(value, str) and not strip_html(value).strip(): if df.fieldtype in ["Text", "Text Editor"]: return True return False elif isinstance(value, list) and not len(value): return False return True
def set_fetch_from_value(self, doctype, df, values): fetch_from_fieldname = df.fetch_from.split('.')[-1] value = values[fetch_from_fieldname] if df.fieldtype == 'Small Text' or df.fieldtype == 'Text' or df.fieldtype == 'Data': if fetch_from_fieldname in default_fields: from frappe.model.meta import get_default_df fetch_from_df = get_default_df(fetch_from_fieldname) else: fetch_from_df = frappe.get_meta(doctype).get_field( fetch_from_fieldname) fetch_from_ft = fetch_from_df.get('fieldtype') if fetch_from_ft == 'Text Editor' and value: value = unescape_html(strip_html(value)) setattr(self, df.fieldname, value)
def column_has_value(data, fieldname): """Check if at least one cell in column has non-zero and non-blank value""" has_value = False for row in data: value = row.get(fieldname) if value: if isinstance(value, basestring): if strip_html(value).strip(): has_value = True break else: has_value = True break return has_value
def column_has_value(data, fieldname): """Check if at least one cell in column has non-zero and non-blank value""" has_value = False for row in data: value = row.get(fieldname) if value: if isinstance(value, string_types): if strip_html(value).strip(): has_value = True break else: has_value = True break return has_value
def test_custom_doctype_medical_record(self): # tests for medical record creation of standard doctypes in test_patient_medical_record.py patient = create_patient() doc = create_doc(patient) # check for medical record medical_rec = frappe.db.exists("Patient Medical Record", { "status": "Open", "reference_name": doc.name }) self.assertTrue(medical_rec) medical_rec = frappe.get_doc("Patient Medical Record", medical_rec) expected_subject = "Date:{0}Rating:3Feedback:Test Patient History Settings".format( frappe.utils.format_date(getdate())) self.assertEqual(strip_html(medical_rec.subject), expected_subject) self.assertEqual(medical_rec.patient, patient) self.assertEqual(medical_rec.communication_date, getdate())
def validate(self): self.validate_reference() if not self.user: self.user = frappe.session.user if not self.subject: self.subject = strip_html((self.content or "")[:141]) if not self.sent_or_received: self.seen = 1 self.sent_or_received = "Sent" self.set_status() self.set_sender_full_name() validate_email(self) set_timeline_doc(self)
def column_has_value(data, fieldname, col_df): """Check if at least one cell in column has non-zero and non-blank value""" has_value = False if col_df.fieldtype in ['Float', 'Currency'] and not col_df.print_hide_if_no_value: return True for row in data: value = row.get(fieldname) if value: if isinstance(value, string_types): if strip_html(value).strip(): has_value = True break else: has_value = True break return has_value
def validate(self): if not self.item_name: self.item_name = self.item_code if not strip_html(cstr(self.description)).strip(): self.description = self.item_name self.validate_uom() self.validate_description() self.add_default_uom_in_conversion_factor_table() self.validate_conversion_factor() self.validate_item_type() self.validate_naming_series() self.check_for_active_boms() self.fill_customer_code() self.check_item_tax() self.validate_barcode() self.validate_warehouse_for_reorder() self.update_bom_item_desc() self.synced_with_hub = 0 self.validate_has_variants() self.validate_attributes_in_variants() self.validate_stock_exists_for_template_item() self.validate_attributes() self.validate_variant_attributes() self.validate_variant_based_on_change() self.validate_fixed_asset() self.clear_retain_sample() self.validate_retain_sample() self.validate_uom_conversion_factor() self.validate_customer_provided_part() self.update_defaults_from_item_group() self.validate_item_defaults() self.validate_auto_reorder_enabled_in_stock_settings() self.cant_change() self.validate_item_tax_net_rate_range() set_item_tax_from_hsn_code(self) if not self.is_new(): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
def set_fetch_from_value(self, doctype, df, values): fetch_from_fieldname = df.fetch_from.split('.')[-1] value = values[fetch_from_fieldname] if df.fieldtype in ['Small Text', 'Text', 'Data']: if fetch_from_fieldname in default_fields: from frappe.model.meta import get_default_df fetch_from_df = get_default_df(fetch_from_fieldname) else: fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname) if not fetch_from_df: frappe.throw( _('Please check the value of "Fetch From" set for field {0}').format(frappe.bold(df.label)), title = _('Wrong Fetch From value') ) fetch_from_ft = fetch_from_df.get('fieldtype') if fetch_from_ft == 'Text Editor' and value: value = unescape_html(strip_html(value)) setattr(self, df.fieldname, value)
def _get_missing_mandatory_fields(self): """Get mandatory fields that do not have any values""" def get_msg(df): if df.fieldtype == "Table": return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label)) elif self.parentfield: return "{}: {} #{}: {}: {}".format(_("Error"), _("Row"), self.idx, _("Value missing for"), _(df.label)) else: return "{}: {}: {}".format(_("Error"), _("Value missing for"), _(df.label)) missing = [] for df in self.meta.get("fields", {"reqd": 1}): if self.get(df.fieldname) in (None, []) or not strip_html(cstr(self.get(df.fieldname))).strip(): missing.append((df.fieldname, get_msg(df))) return missing
def validate(self): if self.reference_doctype and self.reference_name: if not self.reference_owner: self.reference_owner = frappe.db.get_value( self.reference_doctype, self.reference_name, "owner") # prevent communication against a child table if frappe.get_meta(self.reference_doctype).istable: frappe.throw( _("Cannot create a {0} against a child document: {1}" ).format(_(self.communication_type), _(self.reference_doctype))) # Prevent circular linking of Communication DocTypes if self.reference_doctype == "Communication": circular_linking = False doc = get_parent_doc(self) while doc.reference_doctype == "Communication": if get_parent_doc(doc).name == self.name: circular_linking = True break doc = get_parent_doc(doc) if circular_linking: frappe.throw( _("Please make sure the Reference Communication Docs are not circularly linked." ), frappe.CircularLinkingError) if not self.user: self.user = frappe.session.user if not self.subject: self.subject = strip_html((self.content or "")[:141]) if not self.sent_or_received: self.seen = 1 self.sent_or_received = "Sent" self.set_status() self.set_sender_full_name() validate_email(self) set_timeline_doc(self)
def validate(self): self.validate_reference() if not self.user: self.user = frappe.session.user if not self.subject: self.subject = strip_html((self.content or "")[:141]) if not self.sent_or_received: self.seen = 1 self.sent_or_received = "Sent" self.set_status() self.set_sender_full_name() validate_email(self) if self.communication_medium == "Email": self.set_timeline_links() self.deduplicate_timeline_links()
def validate(self): if self.reference_doctype and self.reference_name: if not self.reference_owner: self.reference_owner = frappe.db.get_value(self.reference_doctype, self.reference_name, "owner") # prevent communication against a child table if frappe.get_meta(self.reference_doctype).istable: frappe.throw(_("Cannot create a {0} against a child document: {1}") .format(_(self.communication_type), _(self.reference_doctype))) # Prevent circular linking of Communication DocTypes if self.reference_doctype == "Communication": circular_linking = False doc = get_parent_doc(self) while doc.reference_doctype == "Communication": if get_parent_doc(doc).name==self.name: circular_linking = True break doc = get_parent_doc(doc) if circular_linking: frappe.throw(_("Please make sure the Reference Communication Docs are not circularly linked."), frappe.CircularLinkingError) if not self.user: self.user = frappe.session.user if not self.subject: self.subject = strip_html((self.content or "")[:141]) if not self.sent_or_received: self.seen = 1 self.sent_or_received = "Sent" self.set_status() self.set_sender_full_name() validate_email(self) set_timeline_doc(self)
def render_blocks(context): """returns a dict of block name and its rendered content""" out = {} env = frappe.get_jenv() def _render_blocks(template_path): source = frappe.local.jloader.get_source(frappe.local.jenv, template_path)[0] for referenced_template_path in meta.find_referenced_templates(env.parse(source)): if referenced_template_path: _render_blocks(referenced_template_path) template = frappe.get_template(template_path) for block, render in template.blocks.items(): out[block] = scrub_relative_urls(concat(render(template.new_context(context)))) _render_blocks(context["template"]) # default blocks if not found if "title" not in out and out.get("header"): out["title"] = out["header"] if "title" not in out: out["title"] = context.get("title") if "header" not in out and out.get("title"): out["header"] = out["title"] if out.get("header") and not out["header"].startswith("<h"): out["header"] = "<h2>" + out["header"] + "</h2>" if "breadcrumbs" not in out: out["breadcrumbs"] = scrub_relative_urls( frappe.get_template("templates/includes/breadcrumbs.html").render(context)) if "meta_block" not in out: out["meta_block"] = frappe.get_template("templates/includes/meta_block.html").render(context) out["no_sidebar"] = context.get("no_sidebar", 0) if "<!-- no-sidebar -->" in out.get("content", ""): out["no_sidebar"] = 1 if "<!-- title:" in out.get("content", ""): out["title"] = re.findall('<!-- title:([^>]*) -->', out.get("content"))[0].strip() if "{index}" in out.get("content", "") and context.get("children"): html = frappe.get_template("templates/includes/static_index.html").render({ "items": context["children"]}) out["content"] = out["content"].replace("{index}", html) if "{next}" in out.get("content", ""): next_item = context.doc.get_next() if next_item: if next_item.name[0]!="/": next_item.name = "/" + next_item.name html = '''<p><br><a href="{name}" class="btn btn-primary"> {title} <i class="icon-chevron-right"></i></a> </p>'''.format(**next_item) out["content"] = out["content"].replace("{next}", html) if "sidebar" not in out and not out.get("no_sidebar"): out["sidebar"] = scrub_relative_urls( frappe.get_template("templates/includes/sidebar.html").render(context)) out["title"] = strip_html(out.get("title") or "") # remove style and script tags from blocks out["style"] = re.sub("</?style[^<>]*>", "", out.get("style") or "") out["script"] = re.sub("</?script[^<>]*>", "", out.get("script") or "") return out
def create_sales_invoice(order_dict, order, ebay_site_id, site_id_order, msgprint_log, changes): """ Create a Sales Invoice from the eBay order. """ updated_db = False ebay_order_id = order_dict['ebay_order_id'] ebay_user_id = order_dict['ebay_user_id'] order_fields = db_get_ebay_doc("eBay order", ebay_order_id, fields=[ "name", "customer", "customer_name", "address", "ebay_order_id" ], log=changes, none_ok=False) db_cust_name = order_fields['customer'] # Get from existing linked sales order sinv_fields = db_get_ebay_doc("Sales Invoice", ebay_order_id, fields=["name"], log=changes, none_ok=True) if sinv_fields is not None: # Linked sales invoice exists debug_msgprint('Sales Invoice already exists: ' + ebay_user_id + ' : ' + sinv_fields['name']) changes.append({ "ebay_change": "Sales Invoice already exists", "ebay_user_id": ebay_user_id, "customer_name": order_fields['customer_name'], "customer": db_cust_name, "address": order_fields['address'], "ebay_order": order_fields['name'] }) return # No linked sales invoice - check for old unlinked sales invoice test_title = db_cust_name + "-" + ebay_order_id query = frappe.get_all("Sales Invoice", filters={"title": test_title}) if len(query) > 2: raise ErpnextEbaySyncError( "Multiple Sales Invoices with title {}!".format(test_title)) if len(query) == 1: # Old sales invoice without link - don't interfere debug_msgprint('Old Sales Invoice exists: ' + ebay_user_id + ' : ' + query[0]['name']) changes.append({ "ebay_change": "Old Sales Invoice exists", "ebay_user_id": ebay_user_id, "customer_name": order_fields['customer_name'], "customer": db_cust_name, "address": order_fields['address'], "ebay_order": order_fields['name'] }) return if order['OrderStatus'] != 'Completed': # This order has not been paid yet # Consequently we will not generally have a shipping address, and # so cannot correctly assign VAT. We will therefore not create # the sales invoice yet. debug_msgprint('Order has not yet been paid: ' + ebay_user_id + ' : ' + order_fields['ebay_order_id']) changes.append({ "ebay_change": "Order not yet paid", "ebay_user_id": ebay_user_id, "customer_name": order_fields['customer_name'], "customer": db_cust_name, "address": order_fields['address'], "ebay_order": order_fields['name'] }) return # Create a sales invoice # eBay date format: YYYY-MM-DDTHH:MM:SS.SSSZ posting_date = datetime.datetime.strptime( order['CreatedTime'][:-1] + 'UTC', '%Y-%m-%dT%H:%M:%S.%f%Z') order_status = order['OrderStatus'] item_list = [] payments = [] taxes = [] amount_paid_dict = order['AmountPaid'] if amount_paid_dict['_currencyID'] == 'GBP': amount_paid = float(amount_paid_dict['value']) else: amount_paid = -1.0 sku_list = [] sum_inc_vat = 0.0 sum_exc_vat = 0.0 sum_vat = 0.0 sum_paid = 0.0 shipping_cost = 0.0 transactions = order['TransactionArray']['Transaction'] cust_email = transactions[0]['Buyer']['Email'] # Find the correct VAT rate country_name = order['ShippingAddress']['CountryName'] if country_name is None: raise ErpnextEbaySyncError( 'No country for this order for user {}!'.format(ebay_user_id)) income_account = determine_income_account(country_name) vat_rate = VAT_RATES[income_account] # TODO # Transaction.BuyerCheckoutMessage # Transaction.FinalValueFee # isGSP = TransactionArray.Transaction.ContainingOrder.IsMultiLegShipping # Transaction.ContainingOrder.MonetaryDetails.Payments.Payment.PaymentStatus # Transaction.MonetaryDetails.Payments.Payment.PaymentStatus for transaction in transactions: if transaction['Buyer']['Email'] != cust_email: raise ValueError('Multiple emails for this buyer?') # Vat Status #NoVATTax VAT is not applicable #VATExempt Residence in a country with VAT and user is registered as VAT-exempt #VATTax Residence in a country with VAT and user is not registered as VAT-exempt #vat_status = transaction['Buyer']['VATStatus'] shipping_cost_dict = transaction['ActualShippingCost'] handling_cost_dict = transaction['ActualHandlingCost'] if shipping_cost_dict['_currencyID'] == 'GBP': shipping_cost += float(shipping_cost_dict['value']) if handling_cost_dict['_currencyID'] == 'GBP': shipping_cost += float(handling_cost_dict['value']) qty = float(transaction['QuantityPurchased']) try: sku = transaction['Item']['SKU'] sku_list.append(sku) # Only allow valid SKU except KeyError: debug_msgprint( 'Order {} failed: One of the items did not have an SKU'.format( ebay_order_id)) sync_error(changes, 'An item did not have an SKU', ebay_user_id, customer_name=db_cust_name) raise ErpnextEbaySyncError( 'An item did not have an SKU for user {}'.format(ebay_user_id)) if not frappe.db.exists('Item', sku): debug_msgprint('Item not found?') raise ErpnextEbaySyncError('Item {} not found for user {}'.format( sku, ebay_user_id)) ebay_price = float(transaction['TransactionPrice']['value']) if ebay_price <= 0.0: raise ValueError('TransactionPrice Value <= 0.0') inc_vat = ebay_price exc_vat = round(float(inc_vat) / (1.0 + vat_rate), 2) vat = inc_vat - exc_vat sum_inc_vat += inc_vat sum_exc_vat += exc_vat sum_vat += vat * qty sum_paid += inc_vat * qty # Get item description in case it is empty, and we need to insert # filler text to avoid MandatoryError description = frappe.get_value('Item', sku, 'description') if not strip_html(cstr(description)).strip(): description = '(no item description)' item_list.append({ "item_code": sku, "description": description, "warehouse": "Mamhilad - URTL", "qty": qty, "rate": exc_vat, "valuation_rate": 0.0, "income_account": income_account, "expense_account": "Cost of Goods Sold - URTL", "cost_center": "Main - URTL" }) if shipping_cost > 0.0001: # Add a single line item for shipping services inc_vat = shipping_cost exc_vat = round(float(inc_vat) / (1.0 + vat_rate), 2) vat = inc_vat - exc_vat sum_inc_vat += inc_vat sum_exc_vat += exc_vat sum_vat += vat sum_paid += inc_vat item_list.append({ "item_code": SHIPPING_ITEM, "description": "Shipping costs (from eBay)", "warehouse": "Mamhilad - URTL", "qty": 1.0, "rate": exc_vat, "valuation_rate": 0.0, "income_account": income_account, "expense_account": "Shipping - URTL" }) # Taxes are a single line item not each transaction if VAT_RATES[income_account] > 0.00001: taxes.append({ "charge_type": "Actual", "description": "VAT {}%".format(VAT_PERCENT[income_account]), "account_head": "VAT - URTL", "rate": VAT_PERCENT[income_account], "tax_amount": sum_vat }) checkout = order['CheckoutStatus'] if checkout['Status'] == 'Complete': if checkout['PaymentMethod'] in ('PayOnPickup', 'CashOnPickup'): # Cash on delivery - may not yet be paid (set to zero) payments.append({"mode_of_payment": "Cash", "amount": 0.0}) elif checkout['PaymentMethod'] == 'PayPal': # PayPal - add amount as it has been paid if amount_paid > 0.0: payments.append({ "mode_of_payment": "Paypal", "amount": amount_paid }) elif checkout['PaymentMethod'] == 'PersonalCheck': # Personal cheque - may not yet be paid (set to zero) payments.append({"mode_of_payment": "Cheque", "amount": 0.0}) elif checkout['PaymentMethod'] == 'MOCC': # Postal order/banker's draft - may not yet be paid (set to zero) payments.append({"mode_of_payment": "eBay", "amount": 0.0}) title = 'eBay: {} [{}]'.format(order_fields['customer_name'], ', '.join(sku_list)) sinv_dict = { "doctype": "Sales Invoice", "naming_series": "SINV-", "title": title, "customer": db_cust_name, "ebay_order_id": ebay_order_id, "ebay_site_id": site_id_order, "contact_email": cust_email, "posting_date": posting_date, "posting_time": "00:00:00", "due_date": posting_date, "set_posting_time": 1, "selling_price_list": "Standard Selling", "price_list_currency": "GBP", "price_list_exchange_rate": 1, "ignore_pricing_rule": 1, "apply_discount_on": "Net Total", "status": "Draft", "update_stock": 1, "is_pos": 1, "taxes": taxes, "payments": payments, "items": item_list, "notification_email_address": cust_email, "notify_by_email": 1 } sinv = frappe.get_doc(sinv_dict) sinv.insert() #si.submit() if abs(amount_paid - sum_paid) > 0.005: sinv.add_comment('sync_orders: Unable to match totals - ' + 'please check this order manually.') updated_db = True debug_msgprint('Adding Sales Invoice: ' + ebay_user_id + ' : ' + sinv.name) changes.append({ "ebay_change": "Adding Sales Invoice", "ebay_user_id": ebay_user_id, "customer_name": order_fields['customer_name'], "customer": db_cust_name, "address": order_fields['address'], "ebay_order": order_fields['name'] }) if ebay_site_id and (ebay_site_id != site_id_order): msgprint_log.append( 'WARNING: Sales Invoice {} originated from eBay site {}'.format( sinv.name, site_id_order)) # Commit changes to database if updated_db: frappe.db.commit() return
def create_sales_invoice(order_dict, order, ebay_site_id, site_id_order, msgprint_log, changes): """ Create a Sales Invoice from the eBay order. """ updated_db = False # Don't create SINV from incomplete order if (order['OrderStatus'] != 'Completed' or order['CheckoutStatus']['Status'] != 'Complete'): return ebay_order_id = order_dict['ebay_order_id'] ebay_user_id = order_dict['ebay_user_id'] order_fields = db_get_ebay_doc( "eBay order", ebay_order_id, fields=["name", "customer", "customer_name", "address", "ebay_order_id"], log=changes, none_ok=False) db_cust_name = order_fields['customer'] # Get from existing linked sales order sinv_fields = db_get_ebay_doc( "Sales Invoice", ebay_order_id, fields=["name"], log=changes, none_ok=True) if sinv_fields is not None: # Linked sales invoice exists debug_msgprint('Sales Invoice already exists: ' + ebay_user_id + ' : ' + sinv_fields['name']) changes.append({"ebay_change": "Sales Invoice already exists", "ebay_user_id": ebay_user_id, "customer_name": order_fields['customer_name'], "customer": db_cust_name, "address": order_fields['address'], "ebay_order": order_fields['name']}) return # No linked sales invoice - check for old unlinked sales invoice test_title = db_cust_name + "-" + ebay_order_id query = frappe.get_all("Sales Invoice", filters={"title": test_title}) if len(query) > 2: raise ErpnextEbaySyncError( "Multiple Sales Invoices with title {}!".format(test_title)) if len(query) == 1: # Old sales invoice without link - don't interfere debug_msgprint('Old Sales Invoice exists: ' + ebay_user_id + ' : ' + query[0]['name']) changes.append({"ebay_change": "Old Sales Invoice exists", "ebay_user_id": ebay_user_id, "customer_name": order_fields['customer_name'], "customer": db_cust_name, "address": order_fields['address'], "ebay_order": order_fields['name']}) return # Create a sales invoice # eBay date format: YYYY-MM-DDTHH:MM:SS.SSSZ if 'PaidTime' in order: paid_datetime = order['PaidTime'][:-1] + 'UTC' else: paid_datetime = order['CreatedTime'][:-1] + 'UTC' posting_date = datetime.datetime.strptime(paid_datetime, '%Y-%m-%dT%H:%M:%S.%f%Z') order_status = order['OrderStatus'] buyer_checkout_message = order.get('BuyerCheckoutMessage', None) if buyer_checkout_message: buyer_checkout_message = html.escape(buyer_checkout_message, quote=False) item_list = [] payments = [] taxes = [] amount_paid_dict = order['AmountPaid'] currency = amount_paid_dict['_currencyID'] amount_paid = float(amount_paid_dict['value']) default_currency = get_default_currency() if currency != default_currency: conversion_rate = get_exchange_rate(currency, default_currency, posting_date.date()) else: conversion_rate = 1.0 sku_list = [] sum_inc_vat = 0.0 sum_exc_vat = 0.0 sum_vat = 0.0 sum_to_pay = 0.0 shipping_cost = 0.0 ebay_car = 0.0 # eBay Collect and Remit sales taxes transactions = order['TransactionArray']['Transaction'] cust_email = transactions[0]['Buyer']['Email'] # Find the correct VAT rate country = frappe.db.get_value('Address', order_dict['address'], 'country') if country is None: raise ErpnextEbaySyncError( 'No country for this order for user {}!'.format(ebay_user_id)) income_account = determine_income_account(country) vat_rate = VAT_RATES[income_account] # TODO # isGSP = TransactionArray.Transaction.ContainingOrder.IsMultiLegShipping # Transaction.ContainingOrder.MonetaryDetails.Payments.Payment.PaymentStatus # Transaction.MonetaryDetails.Payments.Payment.PaymentStatus for transaction in transactions: if transaction['Buyer']['Email'] != cust_email: raise ValueError('Multiple emails for this buyer?') # Vat Status #NoVATTax VAT is not applicable #VATExempt Residence in a country with VAT and user is registered as VAT-exempt #VATTax Residence in a country with VAT and user is not registered as VAT-exempt #vat_status = transaction['Buyer']['VATStatus'] shipping_cost_dict = transaction['ActualShippingCost'] handling_cost_dict = transaction['ActualHandlingCost'] final_value_fee_dict = transaction['FinalValueFee'] if shipping_cost_dict['_currencyID'] == currency: shipping_cost += float(shipping_cost_dict['value']) else: raise ErpnextEbaySyncError('Inconsistent currencies in order!') if handling_cost_dict['_currencyID'] == currency: shipping_cost += float(handling_cost_dict['value']) else: raise ErpnextEbaySyncError('Inconsistent currencies in order!') # Final Value Fee currently limited to being in *default* currency or # sale currency, and does not include any VAT (for EU sellers). if final_value_fee_dict['_currencyID'] == default_currency: # final value fee typically in seller currency base_final_value_fee = float(final_value_fee_dict['value']) final_value_fee = base_final_value_fee / conversion_rate elif final_value_fee_dict['_currencyID'] == currency: final_value_fee = float(final_value_fee_dict['value']) base_final_value_fee = final_value_fee * conversion_rate else: raise ErpnextEbaySyncError('Inconsistent currencies in order!') if transaction['eBayCollectAndRemitTax'] == 'true': ebay_car_dict = ( transaction['eBayCollectAndRemitTaxes']['TotalTaxAmount']) if ebay_car_dict['_currencyID'] == currency: ebay_car += float(ebay_car_dict['value']) else: raise ErpnextEbaySyncError('Inconsistent currencies in order!') qty = float(transaction['QuantityPurchased']) try: sku = transaction['Item']['SKU'] sku_list.append(sku) # Only allow valid SKU except KeyError: debug_msgprint( 'Order {} failed: One of the items did not have an SKU'.format( ebay_order_id)) sync_error(changes, 'An item did not have an SKU', ebay_user_id, customer_name=db_cust_name) raise ErpnextEbaySyncError( 'An item did not have an SKU for user {}'.format(ebay_user_id)) if not frappe.db.exists('Item', sku): debug_msgprint('Item not found?') raise ErpnextEbaySyncError( 'Item {} not found for user {}'.format(sku, ebay_user_id)) ebay_price = float(transaction['TransactionPrice']['value']) if ebay_price <= 0.0: raise ValueError('TransactionPrice Value <= 0.0') inc_vat = ebay_price exc_vat = round(float(inc_vat) / (1.0 + vat_rate), 2) vat = inc_vat - exc_vat sum_inc_vat += inc_vat sum_exc_vat += exc_vat sum_vat += vat * qty sum_to_pay += inc_vat * qty # Get item description in case it is empty, and we need to insert # filler text to avoid MandatoryError description = frappe.get_value('Item', sku, 'description') if not strip_html(cstr(description)).strip(): description = '(no item description)' item_list.append({ "item_code": sku, "description": description, "warehouse": WAREHOUSE, "qty": qty, "rate": exc_vat, "ebay_final_value_fee": final_value_fee, "base_ebay_final_value_fee": base_final_value_fee, "valuation_rate": 0.0, "income_account": income_account, "expense_account": f"Cost of Goods Sold - {COMPANY_ACRONYM}", "cost_center": f"Main - {COMPANY_ACRONYM}" }) # Add a single line item for shipping services if shipping_cost > 0.0001: inc_vat = shipping_cost exc_vat = round(float(inc_vat) / (1.0 + vat_rate), 2) vat = inc_vat - exc_vat sum_inc_vat += inc_vat sum_exc_vat += exc_vat sum_vat += vat sum_to_pay += inc_vat item_list.append({ "item_code": SHIPPING_ITEM, "description": "Shipping costs (from eBay)", "warehouse": WAREHOUSE, "qty": 1.0, "rate": exc_vat, "valuation_rate": 0.0, "income_account": income_account, "expense_account": f"Shipping - {COMPANY_ACRONYM}" }) # Add a single line item for eBay Collect and Remit taxes if ebay_car > 0.0001: item_list.append({ "item_code": CAR_ITEM, "description": "eBay Collect and Remit taxes", "warehouse": WAREHOUSE, "qty": 1.0, "rate": ebay_car, "valuation_rate": 0.0, "income_account": income_account, "expense_account": f"Cost of Goods Sold - {COMPANY_ACRONYM}" }) sum_to_pay += ebay_car # Taxes are a single line item not each transaction if VAT_RATES[income_account] > 0.00001: taxes.append({ "charge_type": "Actual", "description": "VAT {}%".format(VAT_PERCENT[income_account]), "account_head": f"VAT - {COMPANY_ACRONYM}", "rate": VAT_PERCENT[income_account], "tax_amount": sum_vat}) checkout = order['CheckoutStatus'] submit_on_pay = False if checkout['PaymentMethod'] in ('PayOnPickup', 'CashOnPickup'): # Cash on delivery - may not yet be paid (set to zero) payments.append({"mode_of_payment": "Cash", "amount": 0.0}) elif checkout['PaymentMethod'] == 'PayPal': # PayPal - add amount as it has been paid paypal_acct = f'PayPal {currency}' if not frappe.db.exists('Mode of Payment', paypal_acct): raise ErpnextEbaySyncError( f'Mode of Payment "{paypal_acct}" does not exist!') if amount_paid > 0.0: payments.append({"mode_of_payment": paypal_acct, "amount": amount_paid}) submit_on_pay = True elif checkout['PaymentMethod'] == 'PersonalCheck': # Personal cheque - may not yet be paid (set to zero) payments.append({"mode_of_payment": "Cheque", "amount": 0.0}) elif checkout['PaymentMethod'] == 'MOCC': # Postal order/banker's draft - may not yet be paid (set to zero) payments.append({"mode_of_payment": "eBay", "amount": 0.0}) title = 'eBay: {} [{}]'.format( order_fields['customer_name'], ', '.join(sku_list)) sinv_dict = { "doctype": "Sales Invoice", "naming_series": "SINV-", "title": title, "customer": db_cust_name, "shipping_address_name": order_dict['address'], "ebay_order_id": ebay_order_id, "ebay_site_id": site_id_order, "buyer_message": buyer_checkout_message, "contact_email": cust_email, "posting_date": posting_date.date(), "posting_time": posting_date.time(), "due_date": posting_date, "set_posting_time": 1, "currency": currency, "conversion_rate": conversion_rate, "ignore_pricing_rule": 1, "apply_discount_on": "Net Total", "status": "Draft", "update_stock": 1, "is_pos": 1, "taxes": taxes, "payments": payments, "items": item_list} sinv = frappe.get_doc(sinv_dict) sinv.insert() if abs(amount_paid - sum_to_pay) > 0.005: sinv.add_comment( 'Comment', text='sync_orders: Unable to match totals - please check this ' + f'order manually ({amount_paid} != {sum_to_pay})') elif submit_on_pay: # This is an order which adds up and has an approved payment method # Submit immediately sinv.submit() updated_db = True debug_msgprint('Adding Sales Invoice: ' + ebay_user_id + ' : ' + sinv.name) changes.append({"ebay_change": "Adding Sales Invoice", "ebay_user_id": ebay_user_id, "customer_name": order_fields['customer_name'], "customer": db_cust_name, "address": order_fields['address'], "ebay_order": order_fields['name']}) # Commit changes to database if updated_db: frappe.db.commit() return
def render_blocks(context): """returns a dict of block name and its rendered content""" out = {} env = frappe.get_jenv() def _render_blocks(template_path): source = frappe.local.jloader.get_source(frappe.local.jenv, template_path)[0] for referenced_template_path in meta.find_referenced_templates( env.parse(source)): if referenced_template_path: _render_blocks(referenced_template_path) template = frappe.get_template(template_path) for block, render in template.blocks.items(): out[block] = scrub_relative_urls( concat(render(template.new_context(context)))) _render_blocks(context["template"]) # default blocks if not found if "title" not in out and out.get("header"): out["title"] = out["header"] if "title" not in out: out["title"] = context.get("title") if "header" not in out and out.get("title"): out["header"] = out["title"] if out.get("header") and not out["header"].startswith("<h"): out["header"] = "<h2>" + out["header"] + "</h2>" if "breadcrumbs" not in out: out["breadcrumbs"] = scrub_relative_urls( frappe.get_template("templates/includes/breadcrumbs.html").render( context)) if "meta_block" not in out: out["meta_block"] = frappe.get_template( "templates/includes/meta_block.html").render(context) out["no_sidebar"] = context.get("no_sidebar", 0) if "<!-- no-sidebar -->" in out.get("content", ""): out["no_sidebar"] = 1 if "<!-- title:" in out.get("content", ""): out["title"] = re.findall('<!-- title:([^>]*) -->', out.get("content"))[0].strip() if "{index}" in out.get("content", "") and context.get("children"): html = frappe.get_template( "templates/includes/static_index.html").render( {"items": context["children"]}) out["content"] = out["content"].replace("{index}", html) if "{next}" in out.get("content", ""): next_item = context.doc.get_next() if next_item: if next_item.name[0] != "/": next_item.name = "/" + next_item.name html = '''<p><br><a href="{name}" class="btn btn-primary"> {title} <i class="icon-chevron-right"></i></a> </p>'''.format(**next_item) out["content"] = out["content"].replace("{next}", html) if "sidebar" not in out and not out.get("no_sidebar"): out["sidebar"] = scrub_relative_urls( frappe.get_template("templates/includes/sidebar.html").render( context)) out["title"] = strip_html(out.get("title") or "") # remove style and script tags from blocks out["style"] = re.sub("</?style[^<>]*>", "", out.get("style") or "") out["script"] = re.sub("</?script[^<>]*>", "", out.get("script") or "") return out