Exemple #1
0
class InlineHelp(Model):
    _name = "inline.help"
    _string = "Help Item"
    _fields = {
        "action": fields.Char("Action Name", required=True, search=True),
        "title": fields.Char("Help Title", required=True, search=True),
        "content": fields.Text("Help Content", required=True, search=True),
        "hide": fields.Boolean("Hide"),
        "create_date": fields.DateTime("Date Created"),
        "modif_date": fields.DateTime("Date Modified"),
    }
    _order = "title"
    _defaults = {
        "create_date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "modif_date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
    }

    def create(self, vals, **kw):
        res = super().create(vals, **kw)
        static.clear_translations()  # XXX: rename this
        return res

    def write(self, ids, vals, **kw):
        super().write(ids, vals, **kw)
        static.clear_translations()  # XXX: rename this

    def delete(self, ids, **kw):
        super().delete(ids, **kw)
        static.clear_translations()  # XXX: rename this
Exemple #2
0
class PayPeriodLine(Model):
    _name = "hr.pay.period.line"
    _string = "Pay Period Line"

    _fields = {
        'period_id': fields.Many2One("hr.pay.period",
                                     "Pay Period",
                                     required=True),
        'time_start': fields.DateTime("Time Start", required=True),
        'time_stop': fields.DateTime("Time Start", required=True),
    }
class TaskLine(Model):
    _name = "my.task.line"
    _fields = {
        'task_id':
        fields.Many2One("my.task", "Task", required=True, on_delete="cascade"),
        "description":
        fields.Text("Description"),
        "time_start":
        fields.DateTime("Start"),
        "time_end":
        fields.DateTime("End"),
    }
Exemple #4
0
class WSEvent(Model):
    _name = "ws.event"
    _log_access = False
    _fields = {
        "listener_id":
        fields.Many2One("ws.listener", "Listener", on_delete="cascade"),
        "name":
        fields.Char("Name", required=True),
        "ctime":
        fields.DateTime("Create Time", required=True),
    }

    def new_event(self, event_name, user_id):
        print("WSEvent.new_event", event_name, user_id)
        t = time.strftime("%Y-%m-%d %H:%M:%S")
        db = get_connection()
        if user_id:
            res = db.query("SELECT id FROM ws_listener WHERE user_id=%s",
                           user_id)
        else:
            res = db.query("SELECT id FROM ws_listener")
        for r in res:
            db.execute(
                "INSERT INTO ws_event (listener_id,name,ctime) VALUES (%s,%s,%s)",
                r.id, event_name, t)
Exemple #5
0
class WSListener(Model):
    _name = "ws.listener"
    _log_access = False
    _fields = {
        "user_id": fields.Many2One("base.user", "User"),
        "last_check_time": fields.DateTime("Last Check Time", required=True),
    }
Exemple #6
0
class PayPeriod(Model):
    _name = "hr.pay.period"
    _string = "Pay Period"

    _fields = {
        "name": fields.Char("Name", required=True, search=True),
        "year": fields.DateTime("Year"),
        'lines': fields.One2Many("hr.pay.period.line", "period_id", "Lines"),
    }
Exemple #7
0
class Operation(Model):
    _name = "production.operation"
    _string = "Production Operation"
    _fields = {
        "order_id": fields.Many2One("production.order", "Production Order", required=True, on_delete="cascade"),
        "workcenter_id": fields.Many2One("workcenter", "Workcenter", required=True),
        "employee_id": fields.Many2One("hr.employee", "Employee"),
        "planned_duration": fields.Decimal("Planned Duration (Minutes)"),
        "time_start": fields.DateTime("Start Time"),
        "time_stop": fields.DateTime("Stop Time"),
        "actual_duration": fields.Decimal("Actual Duration (Minutes)", function="get_actual_duration"),
        "notes": fields.Text("Notes"),
    }

    def get_actual_duration(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.time_start and obj.time_stop:
                t0 = datetime.strptime(obj.time_start, "%Y-%m-%d %H:%M:%S")
                t1 = datetime.strptime(obj.time_stop, "%Y-%m-%d %H:%M:%S")
                vals[obj.id] = math.ceil((t1 - t0).total_seconds() / 60.0)
            else:
                vals[obj.id] = None
        return vals
Exemple #8
0
class EmailEvent(Model):
    _name = "email.event"
    _string = "Email Event"
    _fields = {
        "email_id": fields.Many2One("email.message", "Email", required=True, on_delete="cascade"),
        "type": fields.Selection([["accepted", "Accepted"], ["rejected", "Rejected"], ["delivered", "Delivered"], ["failed", "Failed"], ["opened", "Opened"], ["clicked", "Clicked"], ["unsubscribed", "Unsubscribed"], ["complained", "Complained"], ["stored", "Stored"]], "Event Type", required=True),
        "date": fields.DateTime("Date", required=True),
        "ip_addr": fields.Char("IP Address"),
        "location": fields.Char("Location"),
        "user_agent": fields.Char("User Agent", size=1024),
        "url": fields.Char("URL", size=1024),
        "details": fields.Text("Details"),
    }
    _order = "date"
    _defaults = {
        "date": time.strftime("%Y-%m-%d %H:%M:%S"),
    }
Exemple #9
0
class Attach(Model):
    _name = "attach"
    _string = "Attachment"
    _order = "date desc"
    _fields = {
        "date": fields.DateTime("Date", required=True, search=True),
        "user_id": fields.Many2One("base.user", "User", search=True),
        "file": fields.File("File", required=True),
        "related_id": fields.Reference([["document", "Document"]], "Related To"),
        "description": fields.Text("Description", search=True),
        "comments": fields.One2Many("message", "related_id", "Comments"),
    }

    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "user_id": lambda self, context: int(context.get("user_id")),
    }
Exemple #10
0
class EcomWishList(Model):
    _name = "ecom.wishlist"
    _string = "Product Wishlist"
    _fields = {
        "name":
        fields.Char("Name"),
        "date":
        fields.DateTime("Date"),
        "product_id":
        fields.Many2One("product", "Product", required=True, search=True),
        "contact_id":
        fields.Many2One("contact", "Contact", required=True, search=True),
    }
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
    }
    _order = "date desc"
Exemple #11
0
class Feedback(Model):
    _name = "feedback"
    _fields = {
        "date":
        fields.DateTime("Date", required=True),
        "from_id":
        fields.Many2One("base.user", "From User", required=True),
        "feeling":
        fields.Selection(
            [["good", "Good"], ["indifferent", "Indifferent"], ["bad", "Bad"]],
            "Feeling"),
        "message":
        fields.Text("Message", required=True),
    }
    _order = "date"
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "from_id": lambda self, ctx: ctx.get("user_id"),
    }
Exemple #12
0
class EcomProductReview(Model):
    _name = "ecom.product.review"
    _string = "Product Review"
    _fields = {
        "name":
        fields.Char("Name", required=True, search=True),
        "title":
        fields.Char("Title", required=True, search=True),
        "review":
        fields.Text("Review", required=True),
        "rating":
        fields.Selection([("0", "Not Rate"), ("1", "Bad"), ("2", "Poor"),
                          ("3", "Regular"), ("4", "Good"), ("5", "Gorgeous")],
                         "Rating"),
        "date":
        fields.DateTime("Date"),
        "state":
        fields.Selection([("draft", "Draft"), ("approved", "Approved"),
                          ("discarded", "Discarded")], "State"),
        "product_id":
        fields.Many2One("product", "Product", required=True, search=True),
    }
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "state": "draft",
        "rating": "0"
    }
    _order = "date desc"

    def create(self, vals, **kw):
        id = super().create(vals, **kw)
        for obj in self.browse([id]):
            obj.trigger("created")

    def approve_review(self, ids, context={}):
        self.write(ids, {"state": "approved"})

    def reset_draft(self, ids, context={}):
        self.write(ids, {"state": "draft"})

    def discard_review(self, ids, context={}):
        self.write(ids, {"state": "discarded"})
Exemple #13
0
class FieldCache(Model):
    _name = "field.cache"
    _string = "Field Cache"
    _fields = {
        "model": fields.Char("Model", index=True),
        "field": fields.Char("Field", index=True),
        "record_id": fields.Integer("Record ID", index=True),
        "value": fields.Text("Value"),
        "ctime": fields.DateTime("Time Created"),
    }
    _indexes = [
        ("model", "field", "record_id"),
    ]

    def get_value(self, model, field, ids, min_ctime=None):
        db = get_connection()
        q = "SELECT record_id,value,ctime FROM field_cache WHERE model=%s AND field=%s AND record_id IN %s"
        args = [model, field, tuple(ids)]
        if min_ctime:
            q += " AND ctime>=%s"
            args.append(min_ctime)
        res = db.query(q, *args)
        vals = {}
        for r in res:
            vals[r.record_id] = utils.json_loads(r.value)
        return vals

    def set_value(self, model, field, record_id, value):
        ctime = time.strftime("%Y-%m-%d %H:%M:%S")
        db = get_connection()
        db.execute(
            "DELETE FROM field_cache WHERE model=%s AND field=%s AND record_id=%s",
            model, field, record_id)
        db.execute(
            "INSERT INTO field_cache (model,field,record_id,value,ctime) VALUES (%s,%s,%s,%s,%s)",
            model, field, record_id, utils.json_dumps(value), ctime)

    def clear_cache(self, model):
        db = get_connection()
        db.execute("DELETE FROM field_cache WHERE model=%s", model)
Exemple #14
0
class Log(Model):
    _name = "log"
    _string = "Log Entry"
    _fields = {
        "date": fields.DateTime("Date", required=True, search=True),
        "user_id": fields.Many2One("base.user", "User", search=True),
        "ip_addr": fields.Char("IP Address", search=True),
        "country_id": fields.Many2One("country", "Country", readonly=True),
        "message": fields.Text("Message", required=True, search=True),
        "details": fields.Text("Details", search=True),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "related_id": fields.Reference([], "Related To"),
    }
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
    }
    _order = "id desc"

    def log(self, msg, details=None, ip_addr=None, related_id=None):
        uid = get_active_user()
        if not ip_addr:
            ip_addr = get_ip_addr()
        try:
            country_code = get_ip_country(ip_addr)
            res = get_model("country").search([["code", "=", country_code]])
            country_id = res[0]
        except Exception as e:
            #print("Failed to get IP country: %s"%e)
            country_id = None
        vals = {
            "user_id": uid,
            "ip_addr": ip_addr,
            "message": msg,
            "details": details,
            "country_id": country_id,
            "related_id": related_id,
        }
        set_active_user(1)
        self.create(vals)
        set_active_user(uid)
Exemple #15
0
class Attendance(Model):
    _name = "hr.attendance"
    _string = "Attendance Event"
    _audit_log = True

    _fields = {
        "employee_id":
        fields.Many2One("hr.employee", "Employee", required=True, search=True),
        "time":
        fields.DateTime("Time", required=True, search=True),
        "action":
        fields.Selection([["sign_in", "Sign In"], ["sign_out", "Sign Out"]],
                         "Action",
                         required=True,
                         search=True),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
    }
    _order = "time desc"
    _defaults = {
        "time": lambda *a: time.strftime("%Y-%m-%d %H:%M:S"),
        "user_id": lambda *a: access.get_active_user(),
    }
Exemple #16
0
class Activity(Model):
    _name = "activity"
    _string = "Activity"
    _name_field = "subject"
    _fields = {
        "type":
        fields.Selection([["task", "Task"], ["event", "Event"],
                          ["meeting", "Meeting"], ["call", "Call"]],
                         "Activity Type",
                         required=True,
                         search=True),
        "user_id":
        fields.Many2One("base.user", "Assigned To", search=True,
                        required=True),
        "subject":
        fields.Char("Subject", required=True, size=128, search=True),
        "date":
        fields.Date("Date", search=True),
        "due_date":
        fields.Date("Due Date"),
        "description":
        fields.Text("Description"),
        "body":
        fields.Text("Body"),
        "state":
        fields.Selection(
            [["new", "Not Started"], ["in_progress", "In Progress"],
             ["done", "Completed"], ["waiting", "Waiting on someone else"],
             ["deferred", "Deferred"]],
            "Status",
            required=True),
        "priority":
        fields.Selection(
            [["high", "High"], ["normal", "Normal"], ["low", "Low"]],
            "Priority"),
        "phone":
        fields.Char("Phone"),
        "email":
        fields.Char("Email"),
        "event_start":
        fields.DateTime("Start"),
        "event_end":
        fields.DateTime("End"),
        "location":
        fields.Char("Location"),
        "email_uid":
        fields.Char("Email UID"),
        "email_account_id":
        fields.Many2One("email.account", "Email Account"),
        "work_times":
        fields.One2Many("work.time", "activity_id", "Work Time"),
        "related_id":
        fields.Reference(
            [["contact", "Contact"], ["sale.opportunity", "Opportunity"],
             ["sale.quot", "Quotation"], ["sale.order", "Sales Order"],
             ["job", "Service Order"], ["issue", "Issue"]], "Related To"),
        "name_id":
        fields.Reference([["contact", "Contact"], ["sale.lead", "Lead"]],
                         "Name"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "overdue":
        fields.Boolean("Overdue",
                       function="get_overdue",
                       function_search="search_overdue"),
    }

    def _get_name_id(self, context={}):
        defaults = context.get("defaults")
        if not defaults:
            return
        related = defaults.get("related_id")
        if not related:
            return
        model, model_id = related.split(",")
        model_id = int(model_id)
        if model == "sale.quot":
            obj = get_model("sale.quot").browse(model_id)
            return "contact,%s" % obj.contact_id.id

    _defaults = {
        "state": "new",
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "type": lambda self, ctx: ctx.get("activ_type") or "task",
        "name_id": _get_name_id,
    }
    _order = "due_date desc,date desc,id desc"

    # XXX
    def view_activity(self, ids, context={}):
        obj = self.browse(ids[0])
        return {
            "next": {
                "name": "activ",
                "mode": "form",
                "active_id": obj.id,
            }
        }

    def send_email(self, ids, context={}):
        obj = self.browse(ids)[0]
        from_addr = obj.user_id.email
        to_addr = obj.name_id.email
        if not to_addr:
            raise Exception("Email not found")
        res = get_model("email.account").search([["type", "=", "smtp"]])
        if not res:
            raise Exception("Email account not found")
        acc_id = res[0]
        acc = get_model("email.account").browse(acc_id)
        server = smtplib.SMTP(acc.host, acc.port)
        if acc.user:
            server.login(acc.user, acc.password)
        msg = "From: " + from_addr + "\r\n"
        msg += "To: " + to_addr + "\r\n"
        msg += "Subject: " + obj.subject + "\r\n\r\n"
        msg += obj.body
        server.sendmail(from_addr, [to_addr], msg)
        obj.write({"state": "done"})
        server.quit()

    # XXX: move this
    def fetch_email(self, context={}):
        print("fetch_email")
        acc_ids = get_model("email.account").search([["type", "=", "pop3"]])
        for acc in get_model("email.account").browse(acc_ids):
            print("connecting %s %s %s" % (acc.host, acc.port, acc.user))
            if acc.security == "ssl":
                serv = poplib.POP3_SSL(acc.host, acc.port)
            else:
                serv = poplib.POP3(acc.host, acc.port)
            serv.user(acc.user)
            if acc.password:
                serv.pass_(acc.password)
            try:
                resp, msg_list, size = serv.uidl()
                print("%d messages" % len(msg_list))
                for msg_info in msg_list:
                    msg_no, msg_uid = msg_info.decode().split()
                    print("msg_no", msg_no)
                    print("msg_uid", msg_uid)
                    try:
                        res = self.search_read(
                            [["email_account_id", "=", acc.id],
                             ["email_uid", "=", msg_uid]])
                        if res:
                            print("skipping %s" % msg_uid)
                            serv.dele(msg_no)
                            continue
                        print("reading %s" % msg_uid)
                        resp, lines, size = serv.retr(msg_no)
                        msg = email.message_from_bytes(b"\n".join(lines))

                        def dec_header(data):
                            dh = decode_header(data or "")
                            s = ""
                            for data, charset in dh:
                                if isinstance(data, str):
                                    s += data
                                else:
                                    s += data.decode(
                                        conv_charset(charset) or "utf-8")
                            return s

                        def dec_date(data):
                            res = parsedate(data or "")
                            if not res:
                                return ""
                            return time.strftime("%Y-%m-%d %H:%M:%S", res)

                        def get_body(m):
                            if m.get_filename():
                                return "[Attachment: %s (%s)]\n" % (dec_header(
                                    m.get_filename()), m.get_content_type())
                            else:
                                if not m.is_multipart():
                                    charset = conv_charset(
                                        m.get_content_charset())
                                    return m.get_payload(decode=True).decode(
                                        charset or "utf-8", errors="replace")
                                else:
                                    data = m.get_payload()
                                    found = False
                                    res = []
                                    for m in data:
                                        fn = m.get_filename()
                                        if not fn:
                                            if found:
                                                continue
                                            found = True
                                        res.append(get_body(m))
                                    return "\n".join(res)

                        email_vals = {
                            "account_id": acc.id,
                            "msg_uid": msg_uid,
                            "date": dec_date(msg["Date"]),
                            "from_addr": parseaddr(msg["From"])[1][:64],
                            "to_addr": parseaddr(msg["To"])[1][:64],
                            "subject": dec_header(msg["Subject"])[:128],
                            "body": get_body(msg),
                        }
                        get_model("activity").import_email(email_vals)
                    except Exception as e:
                        print("WARNING: failed to import email %s", msg_uid)
                    finally:
                        serv.dele(msg_no)
            finally:
                print("quitting")
                db = database.get_connection()
                db.commit()
                serv.quit()

    # XXX: move this
    def import_email(self, email):
        print("import_email")
        print("from=%s to=%s subject=%s" %
              (email["from_addr"], email["to_addr"], email["subject"]))
        from_user_id = None
        from_contact_id = None
        from_lead_id = None
        to_user_id = None
        to_contact_id = None
        to_lead_id = None
        user_id = None
        contact_id = None
        lead_id = None
        contact_id = None
        opport_id = None
        quot_id = None
        sale_id = None
        name_id = None
        related_id = None
        res = get_model("base.user").search(
            [["email", "=ilike", email["from_addr"]]])
        if res:
            from_user_id = res[0]
        res = get_model("contact").search(
            [["email", "=ilike", email["from_addr"]]])
        if res:
            from_contact_id = res[0]
        else:
            res = get_model("sale.lead").search(
                [["email", "=ilike", email["from_addr"]]])
            if res:
                from_lead_id = res[0]
        if not from_user_id and not from_contact_id and not from_lead_id:
            print("  => skipping (from)")
            return
        res = get_model("base.user").search(
            [["email", "=ilike", email["to_addr"]]])
        if res:
            to_user_id = res[0]
        res = get_model("contact").search(
            [["email", "=ilike", email["to_addr"]]])
        if res:
            to_contact_id = res[0]
        else:
            res = get_model("sale.lead").search(
                [["email", "=ilike", email["to_addr"]]])
            if res:
                to_lead_id = res[0]
        if (from_contact_id or from_lead_id) and to_user_id:
            if from_contact_id:
                contact_id = from_contact_id
            if from_lead_id:
                lead_id = from_lead_id
            user_id = to_user_id
        elif from_user_id and (to_contact_id or to_lead_id):
            user_id = from_user_id
            if to_contact_id:
                contact_id = to_contact_id
            if to_lead_id:
                lead_id = to_lead_id
        else:
            print("  => skipping (from/to)")
            return
        if contact_id:
            contact = get_model("contact").browse(contact_id)
            if contact.contact_id:
                contact_id = contact.contact_id.id
        if contact_id and email["subject"]:
            subj = email["subject"].lower()
            opp_ids = get_model("sale.opportunity").search(
                [["contact_id", "=", contact_id], ["state", "=", "open"]])
            for opp in get_model("sale.opportunity").browse(opp_ids):
                if subj.find(opp.name.lower()) != -1:
                    opport_id = opp.id
                    break
            quot_ids = get_model("sale.quot").search(
                [["contact_id", "=", contact_id], ["state", "=", "approved"]])
            for quot in get_model("sale.quot").browse(quot_ids):
                if subj.find(quot.number.lower()) != -1:
                    quot_id = quot.id
                    break
            sale_ids = get_model("sale.order").search(
                [["contact_id", "=", contact_id], ["state", "=", "confirmed"]])
            for sale in get_model("sale.order").browse(sale_ids):
                if subj.find(sale.number.lower()) != -1:
                    sale_id = sale.id
                    break
        if contact_id:
            name_id = "contact,%d" % contact_id
        elif lead_id:
            name_id = "sale.lead,%d" % lead_id
        if opport_id:
            related_id = "sale.opportunity,%d" % opport_id
        elif quot_id:
            related_id = "sale.quot,%d" % quot_id
        elif sale_id:
            related_id = "sale.order,%d" % sale_id
        elif contact_id:
            related_id = "contact,%d" % contact_id
        vals = {
            "type": "email",
            "subject": email["subject"],
            "state": "done",
            "user_id": user_id,
            "related_id": related_id,
            "name_id": name_id,
            "email_uid": email["msg_uid"],
            "email_account_id": email["account_id"],
            "body": email["body"],
            "date": email["date"],
        }
        act_id = get_model("activity").create(vals)
        print("  => new activity %d" % act_id)
        return act_id

    def get_overdue(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.due_date:
                vals[obj.id] = obj.due_date < time.strftime(
                    "%Y-%m-%d") and obj.state != "done"
            else:
                vals[obj.id] = False
        return vals

    def search_overdue(self, clause, context={}):
        return [["due_date", "<", time.strftime("%Y-%m-%d")],
                ["state", "!=", "done"]]

    def check_days_before_overdue(self,
                                  ids,
                                  days=None,
                                  days_from=None,
                                  days_to=None,
                                  context={}):
        print("Activity.check_days_before_overdue", ids, days, days_from,
              days_to)
        cond = [["state", "!=", "done"]]
        if days != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days)).strftime("%Y-%m-%d")
            cond.append(["due_date", "=", d])
        if days_from != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days_from)).strftime("%Y-%m-%d")
            print("XXXXXXXXXXXXXxx d", d)
            cond.append(["due_date", "<=", d])
        if days_to != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days_to)).strftime("%Y-%m-%d")
            cond.append(["due_date", ">=", d])
        if ids:
            cond.append(["ids", "in", ids])
        ids = self.search(cond)
        return ids
Exemple #17
0
class StockBalance(Model):
    _name = "stock.balance"
    _string = "Stock Balance"
    _fields = {
        "product_id": fields.Many2One("product", "Product", required=True, search=True),
        "lot_id": fields.Many2One("stock.lot", "Lot / Serial Number", search=True),
        "location_id": fields.Many2One("stock.location", "Location", required=True, search=True),
        "container_id": fields.Many2One("stock.container", "Container", search=True),
        "qty_phys": fields.Decimal("Physical Qty", required=True),
        "qty_virt": fields.Decimal("Virtual Qty", required=True),
        "uom_id": fields.Many2One("uom", "UoM", required=True),
        "min_qty": fields.Decimal("Min Qty"),
        "below_min": fields.Boolean("Below Min", search=True),
        "amount": fields.Decimal("Amount"),
        "last_change": fields.DateTime("Last Change"),
        "supplier_id": fields.Many2One("contact", "Supplier", store=False, search=True, function_search="search_supplier"),
        "qty2": fields.Decimal("Secondary Qty"),
    }
    _order = "product_id.code,location_id"
    _sql_constraints = [
        ("prod_loc_uniq", "unique (product_id, location_id, lot_id, container_id)",
         "Stock balances must have unique products and locations!"),
    ]

    def read(self, *a, **kw):
        self.update_balances()
        res = super().read(*a, **kw)
        return res

    def search(self, *a, **kw):
        self.update_balances()
        res = super().search(*a, **kw)
        return res

    def update_balances(self, context={}):
        #print("UPDATE_BALANCES")
        user_id=access.get_active_user()
        access.set_active_user(1)
        try:
            db = database.get_connection()
            prod_ids = get_model("product").search(
                [["update_balance", "=", True], ["type", "=", "stock"]], context={"active_test": False})  # XXX
            if not prod_ids:
                return
            print("prod_ids", prod_ids)
            db.execute("LOCK stock_balance IN EXCLUSIVE MODE")
            db.execute("DELETE FROM stock_balance WHERE product_id IN %s", tuple(prod_ids))
            loc_ids = get_model("stock.location").search([["type", "=", "internal"]])
            if not loc_ids:
                return
            prod_uoms = {}
            res = db.query("SELECT id,uom_id FROM product WHERE id IN %s", tuple(prod_ids))
            for r in res:
                prod_uoms[r.id] = r.uom_id
            min_qtys = {}
            res = db.query(
                "SELECT location_id,product_id,min_qty,uom_id FROM stock_orderpoint WHERE product_id IN %s", tuple(prod_ids))
            for r in res:
                min_qtys[(r.location_id, r.product_id)] = (r.min_qty, r.uom_id)
            qtys = {}
            res = db.query(
                "SELECT location_to_id,container_to_id,product_id,lot_id,uom_id,state,sum(qty) AS total_qty,sum(cost_amount) AS total_amt,max(date) AS max_date,SUM(qty2) AS total_qty2 FROM stock_move WHERE product_id IN %s AND location_to_id IN %s AND state IN ('pending','approved','done') GROUP BY location_to_id,container_to_id,product_id,lot_id,uom_id,state", tuple(prod_ids), tuple(loc_ids))
            for r in res:
                qtys.setdefault((r.location_to_id, r.container_to_id, r.product_id, r.lot_id), []).append(
                    ("in", r.total_qty, r.total_amt or 0, r.uom_id, r.state, r.max_date, r.total_qty2 or 0))
            res = db.query(
                "SELECT location_from_id,container_from_id,product_id,lot_id,uom_id,state,sum(qty) AS total_qty,sum(cost_amount) AS total_amt,max(date) AS max_date,SUM(qty2) AS total_qty2 FROM stock_move WHERE product_id IN %s AND location_from_id IN %s AND state IN ('pending','approved','done') GROUP BY location_from_id,container_from_id,product_id,lot_id,uom_id,state", tuple(prod_ids), tuple(loc_ids))
            for r in res:
                qtys.setdefault((r.location_from_id, r.container_from_id, r.product_id, r.lot_id), []).append(
                    ("out", r.total_qty, r.total_amt or 0, r.uom_id, r.state, r.max_date, r.total_qty2 or 0))
            bals = {}
            prod_loc_qtys = {}
            for (loc_id, cont_id, prod_id, lot_id), totals in qtys.items():
                last_change = None
                for type, qty, amt, uom_id, state, max_date, qty2 in totals:
                    last_change = last_change and max(max_date, last_change) or max_date
                res = min_qtys.get((loc_id, prod_id))
                if res:
                    min_qty, min_uom_id = res
                else:
                    min_qty = 0
                    min_uom_id = None
                bal_uom_id = prod_uoms[prod_id]
                state_qtys = {}
                state_amts = {}
                state_qtys2 = {}
                for type, qty, amt, uom_id, state, max_date, qty2 in totals:
                    state_qtys.setdefault(state, 0)
                    state_amts.setdefault(state, 0)
                    state_qtys2.setdefault(state, 0)
                    qty_conv = get_model("uom").convert(qty, uom_id, bal_uom_id)
                    if type == "in":
                        state_qtys[state] += qty_conv
                        state_amts[state] += amt
                        state_qtys2[state] += qty2
                    elif type == "out":
                        state_qtys[state] -= qty_conv
                        state_amts[state] -= amt
                        state_qtys2[state] -= qty2
                qty_virt = state_qtys.get("done", 0) + state_qtys.get("pending", 0) + state_qtys.get("approved", 0)
                bals[(loc_id, cont_id, prod_id, lot_id)] = {
                    "qty_phys": state_qtys.get("done", 0),
                    "qty_virt": qty_virt,
                    "amt": state_amts.get("done", 0),
                    "last_change": last_change,
                    "uom_id": bal_uom_id,
                    "min_qty": min_qty and get_model("uom").convert(min_qty, min_uom_id, bal_uom_id) or 0,
                    "qty2": state_qtys2.get("done", 0),
                }
                prod_loc_qtys.setdefault((loc_id, prod_id), 0)
                prod_loc_qtys[(loc_id, prod_id)] += qty_virt
            for (loc_id, prod_id), (min_qty, uom_id) in min_qtys.items():
                if (loc_id, prod_id) not in prod_loc_qtys:
                    bals[(loc_id, None, prod_id, None)] = {
                        "qty_phys": 0,
                        "qty_virt": 0,
                        "amt": 0,
                        "min_qty": min_qty,
                        "uom_id": uom_id,
                        "last_change": None,
                        "qty2": 0,
                    }
            parent_locs = {}
            for loc in get_model("stock.location").search_browse([["parent_id", "!=", None]]):
                parent_locs[loc.id] = loc.parent_id.id
            for (loc_id, cont_id, prod_id, lot_id), bal_vals in list(bals.items()):
                child_id = loc_id
                while True:
                    parent_id = parent_locs.get(child_id)
                    if not parent_id:
                        break
                    k = (parent_id, cont_id, prod_id, lot_id)
                    if k not in bals:
                        bals[k] = {
                            "qty_phys": 0,
                            "qty_virt": 0,
                            "amt": 0,
                            "min_qty": 0,
                            "uom_id": prod_uoms[prod_id],
                            "last_change": None,
                            "qty2": 0,
                        }
                    parent_vals = bals[k]
                    parent_vals["qty_phys"] += bal_vals["qty_phys"]
                    parent_vals["qty_virt"] += bal_vals["qty_virt"]
                    parent_vals["amt"] += bal_vals["amt"]
                    parent_vals["min_qty"] += bal_vals["min_qty"]
                    if bal_vals["last_change"] and (not parent_vals["last_change"] or parent_vals["last_change"] < bal_vals["last_change"]):
                        parent_vals["last_change"] = bal_vals["last_change"]
                    parent_vals["qty2"] += bal_vals["qty2"]
                    child_id = parent_id
            total_virt_qtys={}
            for (loc_id, cont_id, prod_id, lot_id), bal_vals in bals.items():
                total_virt_qtys.setdefault(prod_id,0)
                total_virt_qtys[prod_id]+=bal_vals["qty_virt"]
            below_prods=set()
            for prod_id,qty_virt in total_virt_qtys.items():
                if qty_virt<0: # XXX: take into account min stock rules
                    below_prods.add(prod_id)
            for (loc_id, cont_id, prod_id, lot_id), bal_vals in bals.items():
                bal_vals["below_min"]=prod_id in below_prods
            for (loc_id, cont_id, prod_id, lot_id), bal_vals in bals.items():
                qty_phys = bal_vals["qty_phys"]
                qty_virt = bal_vals["qty_virt"]
                qty2 = bal_vals["qty2"]
                amt = bal_vals["amt"]
                min_qty = bal_vals["min_qty"]
                if qty_phys == 0 and qty_virt == 0 and min_qty == 0 and amt == 0:
                    continue
                prod_loc_qty = prod_loc_qtys.get((loc_id, prod_id), 0)
                below_min = bal_vals["below_min"]
                uom_id = bal_vals["uom_id"]
                last_change = bal_vals["last_change"]
                db.execute("INSERT INTO stock_balance (location_id,container_id,product_id,lot_id,qty_phys,qty_virt,amount,min_qty,uom_id,below_min,last_change,qty2) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
                           loc_id, cont_id, prod_id, lot_id, qty_phys, qty_virt, amt, min_qty, uom_id, below_min, last_change, qty2)
            get_model("product").write(prod_ids, {"update_balance": False})
        finally:
            access.set_active_user(user_id)

    def get_qty_phys(self, location_id, product_id, lot_id):
        cond=[["location_id", "=", location_id], ["product_id", "=", product_id]]
        if lot_id: 
            cond.append(["lot_id","=",lot_id])
        qty=0
        for bal in self.search_browse(cond):
            qty+=bal.qty_phys
        return qty

    def get_qty_virt(self, location_id, product_id, lot_id):
        cond=[["location_id", "=", location_id], ["product_id", "=", product_id]]
        if lot_id: 
            cond.append(["lot_id","=",lot_id])
        qty=0
        for bal in self.search_browse(cond):
            qty+=bal.qty_virt
        return qty

    def get_unit_price(self, location_id, product_id):
        res = self.search([["location_id", "=", location_id], ["product_id", "=", product_id]])
        if not res:
            return 0
        obj = self.browse(res)[0]
        if obj.qty_phys:
            unit_price = obj.amount / obj.qty_phys
        else:
            unit_price = 0
        return unit_price

    def get_prod_qty(self, product_id, loc_type="internal"):
        ids = self.search([["location_id.type", "=", loc_type], ["product_id", "=", product_id]])
        qty = 0
        for obj in self.browse(ids):
            qty += obj.qty_phys
        return qty

    def make_po(self, ids, context={}):
        suppliers = {}
        for obj in self.browse(ids):
            if obj.qty_virt >= obj.min_qty:
                continue
            prod = obj.product_id
            if prod.supply_method!="purchase":
                raise Exception("Supply method for product %s is not set to 'Purchase'"%prod.code)
            res = get_model("stock.orderpoint").search([["product_id", "=", prod.id]])
            if res:
                op = get_model("stock.orderpoint").browse(res)[0]
                max_qty = op.max_qty
            else:
                max_qty = 0
            diff_qty = max_qty - obj.qty_virt
            if prod.purchase_uom_id:
                purch_uom=prod.purchase_uom_id
                if not prod.purchase_to_stock_uom_factor:
                    raise Exception("Missing purchase order -> stock uom factor for product %s"%prod.code)
                purch_qty=diff_qty/prod.purchase_to_stock_uom_factor
            else:
                purch_uom=prod.uom_id
                purch_qty=diff_qty
            if prod.purchase_qty_multiple:
                n=math.ceil(purch_qty/prod.purchase_qty_multiple)
                purch_qty=n*prod.purchase_qty_multiple
            if prod.purchase_uom_id:
                qty_stock=purch_qty*prod.purchase_to_stock_uom_factor
            else:
                qty_stock=None
            line_vals = {
                "product_id": prod.id,
                "description": prod.name_get()[0][1],
                "qty": purch_qty,
                "uom_id": purch_uom.id,
                "unit_price": prod.purchase_price or 0,
                "tax_id": prod.purchase_tax_id.id,
                "qty_stock": qty_stock,
            }
            if not prod.suppliers:
                raise Exception("Missing default supplier for product %s" % prod.name)
            contact_id = prod.suppliers[0].supplier_id.id
            suppliers.setdefault(contact_id, []).append(line_vals)
        if not suppliers:
            raise Exception("Nothing to order")
        count = 0
        for contact_id, lines in suppliers.items():
            vals = {
                "contact_id": contact_id,
                "lines": [("create", x) for x in lines],
            }
            purch_id = get_model("purchase.order").create(vals)
            count += 1
        return {
            "next": {
                "name": "purchase",
                "tab": "Draft",
            },
            "flash": "%d purchase orders created" % count,
        }

    def make_transfer(self, ids, context={}):
        if not ids:
            return
        first = self.browse(ids)[0]
        vals = {
            "location_from_id": first.location_id.id,
        }
        lines = []
        for obj in self.browse(ids):
            lines.append({
                "product_id": obj.product_id.id,
                "lot_id": obj.lot_id.id,
                "qty": obj.qty_phys,
                "uom_id": obj.uom_id.id,
                "container_from_id": obj.container_id.id,
                "container_to_id": obj.container_id.id,
            })
        vals["lines"] = [("create", v) for v in lines]
        new_id = get_model("barcode.transfer").create(vals)
        return {
            "next": {
                "name": "barcode_transfer",
                "active_id": new_id,
            },
        }

    def make_issue(self, ids, context={}):
        if not ids:
            return
        first = self.browse(ids)[0]
        vals = {
            "location_from_id": first.location_id.id,
        }
        lines = []
        for obj in self.browse(ids):
            lines.append({
                "product_id": obj.product_id.id,
                "lot_id": obj.lot_id.id,
                "qty": obj.qty_phys,
                "uom_id": obj.uom_id.id,
                "container_from_id": obj.container_id.id,
            })
        vals["lines"] = [("create", v) for v in lines]
        new_id = get_model("barcode.issue").create(vals)
        return {
            "next": {
                "name": "barcode_issue",
                "active_id": new_id,
            },
        }

    def search_supplier(self, clause, context={}):
        supplier_id = clause[2]
        contact = get_model("contact").browse(supplier_id)
        prod_ids = [p.product_id.id for p in contact.supplied_products]
        return [["product_id", "in", prod_ids]]

    def get_totals(self, product_ids=None, location_ids=None, lot_ids=None, container_ids=None, date_from=None, date_to=None, virt_stock=False):
        print("stock_balance.get_totals product_ids=%s location_ids=%s lot_ids=%s container_ids=%s date_from=%s date_to=%s" % (
            product_ids, location_ids, lot_ids, container_ids, date_from, date_to))
        t0 = time.time()
        q = "SELECT product_id,lot_id,location_from_id,container_from_id,location_to_id,container_to_id,uom_id,SUM(qty) AS total_qty,SUM(cost_amount) AS total_amt,SUM(qty2) AS total_qty2 FROM stock_move WHERE"
        if virt_stock:
            q+=" state in ('pending','approved','done')"
        else:
            q+=" state='done'"
        q_args = []
        if product_ids is not None:
            if product_ids:
                q += " AND product_id IN %s"
                q_args.append(tuple(product_ids))
            else:
                q += " AND false"
        if location_ids is not None:
            if location_ids:
                q += " AND (location_from_id IN %s OR location_to_id IN %s)"
                q_args += [tuple(location_ids), tuple(location_ids)]
            else:
                q += " AND false"
        if lot_ids is not None:
            if lot_ids:
                q += " AND lot_id IN %s"
                q_args.append(tuple(lot_ids))
            else:
                q += " AND false"
        if container_ids is not None:
            if container_ids:
                q += " AND (container_from_id IN %s OR container_to_id IN %s)"
                q_args += [tuple(container_ids), tuple(container_ids)]
            else:
                q += " AND false"
        if date_from:
            q += " AND date>=%s"
            q_args.append(date_from)
        if date_to:
            q += " AND date<=%s"
            q_args.append(date_to)
        q += " GROUP BY product_id,lot_id,location_from_id,container_from_id,location_to_id,container_to_id,uom_id"
        db = database.get_connection()
        res = db.query(q, *q_args)
        totals = {}
        prod_ids = set()
        for r in res:
            prod_ids.add(r.product_id)
        prod_ids = list(prod_ids)
        prod_uoms = {}
        for prod in get_model("product").browse(prod_ids):
            prod_uoms[prod.id] = prod.uom_id.id
        for r in res:
            qty = get_model("uom").convert(r.total_qty, r.uom_id, prod_uoms[r.product_id])
            amt = r.total_amt or 0
            qty2 = r.total_qty2 or 0
            k = (r.product_id, r.lot_id, r.location_from_id, r.container_from_id, r.location_to_id, r.container_to_id)
            tot = totals.setdefault(k, [0, 0, 0])
            tot[0] += qty
            tot[1] += amt
            tot[2] += qty2
        t1 = time.time()
        print("totals size: %d" % len(totals))
        print("get_totals finished in %s ms" % ((t1 - t0) * 1000))
        return totals

    def compute_key_balances(self, keys, context={}):
        print("stock_balance.compute_key_balances", keys)
        t0 = time.time()
        all_prod_ids = set()
        all_loc_ids = set()
        for prod_id, lot_id, loc_id, cont_id in keys:
            all_prod_ids.add(prod_id)
            all_loc_ids.add(loc_id)
        all_prod_ids = list(all_prod_ids)
        all_loc_ids = list(all_loc_ids)
        date_to=context.get("date_to")
        virt_stock=context.get("virt_stock")
        tots = self.get_totals(product_ids=all_prod_ids, location_ids=all_loc_ids, date_to=date_to, virt_stock=virt_stock)
        prod_tots = {}

        def get_sum(prod, lot=None, loc_from=None, loc_to=None, cont_from=None, cont_to=None):
            tot_qty = 0
            tot_amt = 0
            tot_qty2 = 0
            if prod in prod_tots:
                # XXX: can still improve speed
                for (lot_id, loc_from_id, cont_from_id, loc_to_id, cont_to_id), (qty, amt, qty2) in prod_tots[prod].items():
                    if lot and lot_id != lot:
                        continue
                    if loc_from and loc_from_id != loc_from:
                        continue
                    if loc_to and loc_to_id != loc_to:
                        continue
                    if cont_from and cont_from_id != cont_from:
                        continue
                    if cont_to and cont_to_id != cont_to:
                        continue
                    tot_qty += qty
                    tot_amt += amt
                    tot_qty2 += qty2
            return tot_qty, tot_amt, tot_qty2
        for (prod_id, lot_id, loc_from_id, cont_from_id, loc_to_id, cont_to_id), (qty, amt, qty2) in tots.items():
            prod_tots.setdefault(
                prod_id, {})[(lot_id, loc_from_id, cont_from_id, loc_to_id, cont_to_id)] = (qty, amt, qty2)
        bals = {}
        for key in keys:
            prod_id, lot_id, loc_id, cont_id = key
            qty_in, amt_in, qty2_in = get_sum(prod=prod_id, lot=lot_id, loc_to=loc_id, cont_to=cont_id)
            qty_out, amt_out, qty2_out = get_sum(prod=prod_id, lot=lot_id, loc_from=loc_id, cont_from=cont_id)
            qty = qty_in - qty_out
            amt = amt_in - amt_out
            qty2 = qty2_in - qty2_out
            bals[key] = [qty, amt, qty2]
        t1 = time.time()
        print("compute_key_balances finished in %d ms" % ((t1 - t0) * 1000))
        return bals
Exemple #18
0
class CronJob(Model):
    _name = "cron.job"
    _string = "Job"
    _fields = {
        "name":
        fields.Char("Description", required=True),
        "date":
        fields.DateTime("Scheduled Date"),
        "model":
        fields.Char("Model"),
        "method":
        fields.Char("Method"),
        "args":
        fields.Text("Arguments"),
        "state":
        fields.Selection([["waiting", "Waiting"], ["running", "Running"],
                          ["done", "Done"], ["canceled", "Canceled"]],
                         "Status",
                         required=True),
        "interval_num":
        fields.Integer("Interval Number"),
        "interval_type":
        fields.Selection([["second", "Second"], ["minute", "Minute"],
                          ["hour", "Hour"], ["day", "Day"]], "Interval Unit"),
        "call_num":
        fields.Integer("Number Of Calls"),
        "date_start":
        fields.DateTime("Start Date"),
        "date_stop":
        fields.DateTime("Stop Date"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "last_error_time":
        fields.DateTime("Last Error Time"),
        "error_message":
        fields.Text("Error Message"),
        "timeout":
        fields.Integer("Timeout (s)"),
    }
    _order = "date desc"
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "state": "waiting",
    }

    def create(self, vals, **kw):
        new_id = super().create(vals, **kw)
        netforce.job.force_check_jobs()
        return new_id

    def write(self, ids, vals, **kw):
        super().write(ids, vals, **kw)
        netforce.job.force_check_jobs()

    def schedule_job(self, model, method, *args):
        vals = {
            "name": "/",
            "model": model,
            "method": method,
            "args": json.dumps(args),
        }
        return self.create(vals)

    def trigger_event(self, event, context={}):
        print("CronJob.trigger_event", event)
        db = get_connection()
        res = db.query(
            "SELECT DISTINCT(tm.name) AS trigger_model FROM wkf_rule r,model tm WHERE tm.id=r.trigger_model_id AND r.trigger_event=%s AND r.state='active'",
            event)
        models = sorted([r.trigger_model for r in res])
        for model in models:
            m = get_model(model)
            m.trigger(None, event)

    def get_alert(self, context={}):
        res = self.search([["state", "=", "error"]])
        if not res:
            return None
        return {
            "type": "error",
            "title": "WARNING",
            "text": "There are scheduled jobs with 'Error' status",
        }
Exemple #19
0
class Task(Model):
    _name = "task"
    _string = "Task"
    _name_field = "title"
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "sequence":
        fields.Integer("Sequence"),
        "date_created":
        fields.DateTime("Date Created", required=True, search=True),
        "project_id":
        fields.Many2One("project", "Project", required=True, search=True),
        "milestone_id":
        fields.Many2One("project.milestone", "Milestone", search=True),
        "task_list_id":
        fields.Many2One("task.list", "Task List", search=True),
        "job_id":
        fields.Many2One("job", "Service Order", search=True),
        "contact_id":
        fields.Many2One("contact",
                        "Customer",
                        function="_get_related",
                        function_context={"path": "project_id.contact_id"}),
        "title":
        fields.Char("Title", required=True, search=True),
        "description":
        fields.Text("Description", search=True),
        "progress":
        fields.Integer("Progress (%)"),
        "date_start":
        fields.Date("Start Date", required=True),
        "date_end":
        fields.Date("End Date", function="get_end_date", store=True),
        "duration":
        fields.Integer("Duration (Days)", required=True),
        "due_date":
        fields.Date("Due Date"),
        "done_date":
        fields.Date("Completion Date"),
        "resource_id":
        fields.Many2One("service.resource", "Assigned To"),  # XXX: deprecated
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "comments":
        fields.Text("Comments"),
        "messages":
        fields.One2Many("message", "related_id", "Messages"),
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "state":
        fields.Selection([["open", "Open"], ["closed", "Closed"]],
                         "Status",
                         required=True,
                         search=True),
        "depends":
        fields.One2Many("task.depend", "task_id", "Task Dependencies"),
        "related_id":
        fields.Reference([["job", "Job"]], "Related To"),
        "depends_json":
        fields.Text("Task Dependencies (String)", function="get_depends_json"),
        "assignments":
        fields.One2Many("task.assign", "task_id", "Assignments"),
    }
    _order = "project_id.start_date,milestone_id.plan_date_from,task_list_id.date_created,date_start,id"  # XXX

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence(type="task")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id)
            user_id = access.get_active_user()
            access.set_active_user(1)
            res = self.search([["number", "=", num]])
            access.set_active_user(user_id)
            if not res:
                return num
            get_model("sequence").increment_number(seq_id)

    _defaults = {
        "date_created": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "state": "new",
        "number": _get_number,
    }

    def get_depends_json(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            res = []
            for dep in obj.depends:
                res.append((dep.id, dep.prev_task_id.id, dep.delay))
            vals[obj.id] = res
        return vals

    def calc_end_date(self, date_start, duration):
        d = datetime.strptime(date_start, "%Y-%m-%d")
        dur = 0
        while True:
            if not is_holiday(d):
                dur += 1
            if dur >= duration:
                break
            d += timedelta(days=1)
        return d.strftime("%Y-%m-%d")

    def update_end(self, context={}):
        data = context.get("data", {})
        date_start = data["date_start"]
        duration = data.get("duration", 0)
        data["date_end"] = self.calc_end_date(date_start, duration)
        return data

    def get_end_date(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = self.calc_end_date(obj.date_start, obj.duration)
        return vals

    def create(self, vals, *args, **kw):
        new_id = super().create(vals, *args, **kw)
        self.function_store([new_id])
        return new_id

    def write(self, ids, *args, **kw):
        super().write(ids, *args, **kw)
        self.function_store(ids)

    def add_link(self, source_id, target_id, context={}):
        vals = {
            "prev_task_id": source_id,
            "task_id": target_id,
        }
        get_model("task.depend").create(vals)

    def delete_link(self, link_ids, context={}):
        get_model("task.depend").delete(link_ids)
Exemple #20
0
class Employee(Model):
    _name = "hr.employee"
    _string = "Employee"
    _name_field = "first_name"  # XXX
    _multi_company = True
    _key = ["code", "company_id"]
    _export_field = "code"

    _fields = {
        "code":
        fields.Char("Employee Code", search=True),
        "department_id":
        fields.Many2One("hr.department", "Department", search=True),
        "title":
        fields.Selection(
            [["mr", "Mr."], ["mrs", "Mrs."], ["miss", "Miss"], ["ms", "Ms."]],
            "Title"),
        "first_name":
        fields.Char("First Name", search=True, translate=True),
        "last_name":
        fields.Char("Last Name", required=True, search=True, translate=True),
        "hire_date":
        fields.Date("Hire Date"),
        "work_status":
        fields.Selection([["working", "Working"], ["dismissed", "Dismissed"],
                          ["resigned", "Resigned"], ["died", "Died"]],
                         "Work Status"),
        "work_type":
        fields.Selection(
            [["monthly", "Monthly"], ["daily", "Daily"], ["hourly", "Job"]],
            "Work Type"),
        "resign_date":
        fields.Date("Resign Date"),
        "position":
        fields.Char("Position", search=True),
        "birth_date":
        fields.Date("Birth Date"),
        "age":
        fields.Integer("Age", function="get_age"),
        "gender":
        fields.Selection([["male", "Male"], ["female", "Female"]], "Gender"),
        "marital_status":
        fields.Selection([["single", "Single"], ["married", "Married"],
                          ["divorced", "Divorced"], ["widowed", "Widowed"]],
                         "Marital Status"),
        "addresses":
        fields.One2Many("address", "employee_id", "Address"),
        "id_no":
        fields.Char("ID No."),
        "drive_license_type":
        fields.Selection([["car", "Car"], ['motorcycle', 'Motorcycle']],
                         "Driving License"),
        "drive_license_no":
        fields.Char("Driving License No."),
        "country_id":
        fields.Many2One("country", "Country"),
        "bank_account":
        fields.Char("Bank Account"),
        "salary":
        fields.Decimal("Salary"),
        "picture":
        fields.File("Picture"),
        "tax_no":
        fields.Char("Taxpayer ID No."),
        "spouse_first_name":
        fields.Char("Spouse First Name"),
        "spouse_last_name":
        fields.Char("Spouse Last Name"),
        "spouse_title":
        fields.Selection([["mr", "Mr."], ["ms", "Ms."]], "Spouse Title"),
        "spouse_birth_date":
        fields.Date("Spouse Birth Date"),
        "spouse_tax_no":
        fields.Char("Spouse Tax ID No"),
        "spouse_status":
        fields.Selection(
            [["married", "Married existed throughout this tax year"],
             ["married_new", "Married during this tax year"],
             ["divorced", "Divorced during tax year"],
             ["deceased", "Deceased during tax year"]], "Spouse Status"),
        "spouse_filing_status":
        fields.Selection(
            [["joint", "Has income and file joint return"],
             ["separate", "Has income and file separate tax return"],
             ["no_income", "Has no income"]], "Spouse Filing Status"),
        "num_child1":
        fields.Integer("No. of Children #1 (C3)"),
        "num_child2":
        fields.Integer("No. of Children #2 (C3)"),
        "social_no":
        fields.Char("Social No."),
        "social_register":
        fields.Boolean("Register Soc. Secur."),
        "social_calc_method":
        fields.Selection(
            [["regular", "Regular Rate"], ["none", "Not Participate"],
             ["special", "Special Rate"]], "Calc. Method"),
        "prov_fund_no":
        fields.Char("Prov. Fund No."),
        "prov_open_date":
        fields.Char("Opened Prov. Fund A/C Date"),
        "prov_rate_employer":
        fields.Decimal("Employer Contribution (%)"),
        "prov_rate_employee":
        fields.Decimal("Employee Contribution (%)"),
        "gov_pension_fund":
        fields.Decimal("Gov. Pension Fund Amount (B2)"),
        "teacher_fund":
        fields.Decimal("Teacher Aid Fund Amount (B3)"),
        "old_disabled":
        fields.Decimal("Older than 65 or disabled (personal, B4)"),
        "old_disabled_spouse":
        fields.Decimal("Older than 65 or disabled (spouse, B5)"),
        "severance_pay":
        fields.Decimal("Severance Pay (B6)"),
        "education_donation":
        fields.Decimal("Education Donations (A8)"),
        "other_donation":
        fields.Decimal("Other Donations (A10)"),
        "house_deduct":
        fields.Decimal("Exemption for home buyer (A13)"),
        "wht_amount":
        fields.Decimal("Withholding Tax Amount (A15)"),
        "father_id_no":
        fields.Char("Father ID No. (C4)"),
        "mother_id_no":
        fields.Char("Mother ID No. (C4)"),
        "spouse_father_id_no":
        fields.Char("Father of spouse ID No. (C4)"),
        "spouse_mother_id_no":
        fields.Char("Mother of spouse ID No. (C4)"),
        "disabled_support":
        fields.Decimal("Disabled person support (C5)"),
        "parent_health_insurance":
        fields.Decimal("Parent Health Insurance (C6)"),
        "life_insurance":
        fields.Decimal("Life Insurance (C7)"),
        "retirement_mutual_fund":
        fields.Decimal("Retirement Mutual Fund (C9)"),
        "long_term_equity_fund":
        fields.Decimal("Long Term Equity Fund (C10)"),
        "interest_residence":
        fields.Decimal("Interest paid for residence (C11)"),
        "other_deduct":
        fields.Decimal("Other Deductions (C12)"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "active":
        fields.Boolean("Active"),
        "time_in":
        fields.DateTime("Last Sign In",
                        function="get_attend",
                        function_multi=True),
        "time_out":
        fields.DateTime("Last Sign Out",
                        function="get_attend",
                        function_multi=True),
        "attend_state":
        fields.Selection([["absent", "Absent"], ["present", "Present"]],
                         "Status",
                         function="get_attend",
                         function_multi=True),
        "user_id":
        fields.Many2One("base.user", "User", search=True),
        "payslips":
        fields.One2Many("hr.payslip", "employee_id", "Payslips"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "phone":
        fields.Char("Phone", search=True),
        "approver_id":
        fields.Many2One("base.user", "Approver"),
        "company_id":
        fields.Many2One("company", "Company"),
        "leave_types":
        fields.Many2Many("hr.leave.type", "Leave Types"),
        "attendance_id":
        fields.Integer("Attendance ID"),
        "email":
        fields.Char("Email", search=True),
        'profile_id':
        fields.Many2One("hr.payitem.profile", "Pay Item Profile"),
        'schedule_id':
        fields.Many2One("hr.schedule", "Working Schedule"),
        'leaves':
        fields.One2Many('hr.leave', 'employee_id', 'Leaves'),
    }

    def _get_code(self, context={}):
        while 1:
            code = get_model("sequence").get_number("employee")
            if not code:
                return None
            res = self.search([["code", "=", code]])
            if not res:
                return code
            get_model("sequence").increment("employee")

    _defaults = {
        "active": True,
        "work_status": "working",
        "code": _get_code,
        "company_id": lambda *a: get_active_company(),
    }
    _order = "code,first_name,last_name"

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            if obj.first_name:
                name = obj.first_name + " " + obj.last_name
            else:
                name = obj.last_name
            if obj.code:
                name += " [%s]" % obj.code
            vals.append((obj.id, name))
        return vals

    def name_search(self, name, condition=[], limit=None, context={}):
        cond = [[
            "or", ["first_name", "ilike", "%" + name + "%"],
            ["last_name", "ilike", "%" + name + "%"],
            ["code", "ilike", "%" + name + "%"]
        ], condition]
        ids = self.search(cond, limit=limit)
        return self.name_get(ids, context)

    def get_age(self, ids, context={}):
        vals = {}
        cr_year = int(time.strftime('%Y'))
        for obj in self.browse(ids):
            if obj.birth_date:
                age = cr_year - int(obj.birth_date[0:4])
            else:
                age = 0
            vals[obj.id] = age
        return vals

    def get_attend(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            # user_id=obj.user_id.id
            # if user_id:
            # db=database.get_connection()
            #res=db.get("SELECT MAX(time) AS time_in FROM hr_attendance WHERE user_id=%s AND action='sign_in'",user_id)
            # time_in=res.time_in
            #res=db.get("SELECT MAX(time) AS time_out FROM hr_attendance WHERE user_id=%s AND action='sign_out'",user_id)
            # time_out=res.time_out
            # else:
            # time_in=None
            # time_out=None
            db = database.get_connection()
            res = db.get(
                "SELECT MAX(time) AS time_in FROM hr_attendance WHERE employee_id=%s AND action='sign_in'",
                obj.id)
            time_in = res.time_in
            res = db.get(
                "SELECT MAX(time) AS time_out FROM hr_attendance WHERE employee_id=%s AND action='sign_out'",
                obj.id)
            time_out = res.time_out
            if time_in:
                if time_out and time_out > time_in:
                    state = "absent"
                else:
                    today = time.strftime("%Y-%m-%d")
                    if time_in.startswith(today):
                        state = "present"
                    else:
                        state = "absent"
                    # should not show timeout of anotherday
                    # if not time_out.startswith(today):
                    # time_out=None
            else:
                state = "absent"
            vals[obj.id] = {
                "time_in": time_in,
                "time_out": time_out,
                "attend_state": state,
            }
        return vals

    def get_address(self, ids, context={}):
        obj = self.browse(ids)[0]
        if not obj.addresses:
            return ""
        addr = obj.addresses[0]
        res = addr.get_address_text()
        return res[addr.id]

    def onchange_num_child(self, context={}):
        data = context["data"]
        setting = get_model("hr.payroll.settings").browse(1)
        child_alw_limit = setting.child_alw_limit or 0
        child_total = (data['num_child1'] or 0) + (data['num_child2'] or 0)
        if child_alw_limit and child_total > child_alw_limit:
            data['num_child1'] = 0
            data['num_child2'] = 0
        return data
Exemple #21
0
class User(Model):
    _name = "base.user"
    _key = ["login"]
    _name_field = "login"
    _string = "User"
    _fields = {
        "name":
        fields.Char("Name", required=True, search=True),
        "login":
        fields.Char("Login", required=True, search=True),
        "password":
        fields.Char("Password", password=True, size=256),
        "email":
        fields.Char("Email", search=True),
        "mobile":
        fields.Char("Mobile"),
        "role_id":
        fields.Many2One("role", "Role"),
        "profile_id":
        fields.Many2One("profile", "Profile", required=True, search=True),
        "lastlog":
        fields.DateTime("Last login"),
        "active":
        fields.Boolean("Active"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "online_status":
        fields.Selection([["offline", "Offline"], ["online", "Online"]],
                         "Status",
                         function="get_online_status"),
        "contact_id":
        fields.Many2One("contact", "Contact"),
        "pin_code":
        fields.Char("PIN Code", password=True, size=256),
        "company_id":
        fields.Many2One("company", "Company", search=True),
        "company2_id":
        fields.Many2One("company", "Company #2", search=True),
        "password_reset_code":
        fields.Char("Password Reset Code"),
    }
    _order = "login"
    _defaults = {
        "activ_code": lambda *a: "%.x" % random.randint(0, 1 << 32),
        "active": True,
    }

    def name_search(self, name, condition=[], limit=None, context={}):
        cond = [[
            "or", ["name", "ilike", "%" + name + "%"],
            ["login", "ilike", "%" + name + "%"]
        ], condition]
        ids = self.search(cond, limit=limit)
        return self.name_get(ids, context)

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            name = "%s (%s)" % (obj.name, obj.login)
            vals.append([obj.id, name])
        return vals

    def disable_users(self, context={}):
        max_users = self.get_max_users()
        if max_users is None:
            return
        db = database.get_connection()
        num_users = db.get("SELECT COUNT(*) FROM base_user WHERE active").count
        if num_users <= max_users:
            return
        res = db.get(
            "SELECT id FROM base_user WHERE active ORDER BY id OFFSET %d LIMIT 1"
            % max_users)
        user_id = res.id
        db.execute("UPDATE base_user SET active=false WHERE id>=%d" % user_id)

    def delete(self, ids, **kw):
        if 1 in ids:
            raise Exception("Can not delete root user (id=1)")
        super().delete(ids, **kw)

    def send_activ_email(self, ids, context={}):
        res = get_model("email.account").search([["type", "=", "smtp"]])
        if not res:
            raise Exception("Email account not found")
        smtp_id = res[0]
        for user in self.browse(ids):
            from_addr = "*****@*****.**"
            to_addr = user.email
            subject = "Welcome to Netforce!"
            body = """Welcome to Netforce and thanks for signing up!

Click on the link below to activate your account.
http://nf1.netforce.com/action?name=nfw_activate&activ_code=%s""" % user.activ_code
            vals = {
                "type": "out",
                "account_id": smtp_id,
                "from_addr": from_addr,
                "to_addr": to_addr,
                "subject": subject,
                "body": body,
            }
            msg_id = get_model("email.message").create(vals)
            get_model("email.message").send([msg_id])

    def send_password_reset_email(self, ids, context={}):
        res = get_model("email.account").search([["type", "=", "smtp"]])
        if not res:
            raise Exception("Email account not found")
        smtp_id = res[0]
        for user in self.browse(ids):
            code = "%.x" % random.randint(0, 1 << 32)
            user.write({"reset_code": code})
            from_addr = "*****@*****.**"
            to_addr = user.email
            subject = "Netforce password reset"
            body = """Click on the link below to reset your password.
http://nf1.netforce.com/action?name=nfw_reset_passwd&reset_code=%s""" % code
            vals = {
                "type": "out",
                "account_id": smtp_id,
                "from_addr": from_addr,
                "to_addr": to_addr,
                "subject": subject,
                "body": body,
            }
            msg_id = get_model("email.message").create(vals)
            get_model("email.message").send([msg_id])

    def get_online_status(self, ids, context={}):
        vals = {}
        db = database.get_connection()
        res = db.query("SELECT user_id FROM ws_listener")
        online_ids = set([r.user_id for r in res])
        for obj in self.browse(ids):
            vals[obj.id] = obj.id in online_ids and "online" or "offline"
        return vals

    def check_password(self, login, password, context={}):
        db = database.get_connection()
        res = db.get("SELECT id,password FROM base_user WHERE login ILIKE %s",
                     login)
        if not res:
            return None
        if not utils.check_password(password, res.password):
            return None
        return res.id

    def check_pin_code(self, ids, pin_code, context={}):
        user_id = ids[0]
        db = database.get_connection()
        res = db.get("SELECT pin_code FROM base_user WHERE id=%s", user_id)
        if not res:
            return None
        if not utils.check_password(pin_code, res.pin_code):
            return None
        return True

    def get_ui_params(self, context={}):
        user_id = access.get_active_user()
        if not user_id:
            return
        try:
            access.set_active_user(1)
            db = database.get_connection()
            if not db:
                return
            user = self.browse(user_id)
            params = {
                "name": user.name,
            }
            prof = user.profile_id
            params["default_model_perms"] = prof.default_model_perms
            params["model_perms"] = []
            for p in prof.perms:
                params["model_perms"].append({
                    "model": p.model_id.name,
                    "perm_read": p.perm_read,
                    "perm_create": p.perm_create,
                    "perm_write": p.perm_write,
                    "perm_delete": p.perm_delete,
                })
            params["field_perms"] = []
            for p in prof.field_perms:
                params["field_perms"].append({
                    "model": p.field_id.model_id.name,
                    "field": p.field_id.name,
                    "perm_read": p.perm_read,
                    "perm_write": p.perm_write,
                })
            params["default_menu_access"] = prof.default_menu_access
            params["menu_perms"] = []
            for p in prof.menu_perms:
                params["menu_perms"].append({
                    "action": p.action,
                    "menu": p.menu,
                    "access": p.access,
                })
            params["other_perms"] = [p.code for p in prof.other_perms]
            return params
        finally:
            access.set_active_user(user_id)

    def password_reset(self, ids, context={}):
        obj = self.browse(ids)
        random_no = "%012d" % random.randint(0, 999999999999)
        obj.write({'password_reset_code': random_no})
        return random_no
Exemple #22
0
class Job(Model):
    _name = "job"
    _string = "Service Order"
    _name_field = "number"
    _audit_log = True
    _multi_company = True
    _fields = {
        "project_id": fields.Many2One("project", "Project", search=True),
        "contact_id": fields.Many2One("contact", "Customer", required=True, search=True),
        "template_id": fields.Many2One("job.template", "Template"),
        "service_type_id": fields.Many2One("service.type", "Service Type", search=True),
        "product_id": fields.Many2One("product", "Product"),  # XXX: deprecated
        "name": fields.Char("Order Name", search=True),
        "number": fields.Char("Order Number", required=True, search=True),
        "description": fields.Text("Description"),
        "due_date": fields.Date("Due Date", search=True),
        "close_date": fields.Date("Close Date", search=True),
        "priority": fields.Selection([["low", "Low"], ["medium", "Medium"], ["high", "High"]], "Priority", search=True),
        "state": fields.Selection([["planned", "Planned"], ["allocated", "Allocated"], ["in_progress", "In Progress"], ["done", "Completed"], ["canceled", "Canceled"]], "Status", required=True),
        "overdue": fields.Boolean("Overdue", function="get_overdue", function_search="search_overdue"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "documents": fields.One2Many("document", "related_id", "Documents"),
        "tasks": fields.One2Many("task", "job_id", "Tasks"),
        "days_late": fields.Integer("Days Late", function="get_days_late"),
        "user_id": fields.Many2One("base.user", "Assigned To"),  # XXX: deprecated
        "resource_id": fields.Many2One("service.resource", "Assigned Resource", search=True),  # XXX: deprecated
        "skill_level_id": fields.Many2One("skill.level", "Required Skill Level", search=True),
        "request_by_id": fields.Many2One("base.user", "Requested By", search=True),
        "user_board_id": fields.Boolean("User", store=False, function_search="search_user_board_id"),
        "sharing": fields.One2Many("share.record", "related_id", "Sharing"),
        "invoice_no": fields.Char("Invoice No."),  # XXX: not used any more...
        "shared_board": fields.Boolean("Shared", store=False, function_search="search_shared_board"),
        "quotation_id": fields.Many2One("sale.quot", "Quotation"),
        "cancel_reason": fields.Text("Cancel Reason"),
        "cancel_periodic": fields.Boolean("Cancel Periodic"),
        "next_job_id": fields.Many2One("job", "Next Order"),
        "emails": fields.One2Many("email.message", "related_id", "Emails"),
        "company_id": fields.Many2One("company", "Company"),
        "invoices": fields.One2Many("account.invoice", "related_id", "Invoices"),
        "bill_amount": fields.Decimal("Billable Amount"),
        "invoice_id": fields.Many2One("account.invoice", "Invoice"),
        "is_duplicate": fields.Boolean("Duplicate"),
        "work_time": fields.One2Many("work.time", "job_id", "Work Time"),
        "pickings": fields.One2Many("stock.picking", "related_id", "Pickings"),
        "stock_moves": fields.One2Many("stock.move", "related_id", "Stock Movements"),
        "parts": fields.One2Many("job.part", "job_id", "Parts"),
        "other_costs": fields.One2Many("job.cost", "job_id", "Other Costs"),
        "items": fields.One2Many("job.item", "job_id", "Service Items"),
        "allocs": fields.One2Many("service.resource.alloc", "job_id", "Resource Allocations"),
        "time_start": fields.DateTime("Planned Start Time"),
        "time_stop": fields.DateTime("Planned Stop Time"),
        "location_id": fields.Many2One("stock.location", "Job Location"),
        "related_id": fields.Reference([["sale.order", "Sales Order"], ["rental.order","Rental Order"], ["issue", "Issue"]], "Related To"),
        "lines": fields.One2Many("job.line", "job_id", "Worksheet"),
        "complaints": fields.Text("Complaints"),
        "cause": fields.Text("Cause"),
        "correction": fields.Text("Correction"),
        "amount_total": fields.Decimal("Total Selling", function="get_total", function_multi=True),
        "amount_contract": fields.Decimal("Included In Contract", function="get_total", function_multi=True),
        "amount_job": fields.Decimal("Not Included In Contract", function="get_total", function_multi=True),
        "overdue": fields.Boolean("Overdue", function="get_overdue", function_search="search_overdue"),
        "date_open": fields.DateTime("Actual Start"),
        "date_close": fields.DateTime("Actual Stop"),
        "labor_cost": fields.Decimal("Labor Cost", function="get_cost", function_multi=True),
        "part_cost": fields.Decimal("Parts Cost", function="get_cost", function_multi=True),
        "other_cost": fields.Decimal("Other Cost", function="get_cost", function_multi=True),
        "total_cost": fields.Decimal("Total Cost", function="get_cost", function_multi=True),
        "labor_sell": fields.Decimal("Labor Selling", function="get_sell", function_multi=True),
        "part_sell": fields.Decimal("Parts Selling", function="get_sell", function_multi=True),
        "other_sell": fields.Decimal("Other Selling", function="get_sell", function_multi=True),
        "done_approved_by_id": fields.Many2One("base.user", "Approved By", readonly=True),
        "multi_visit_code_id": fields.Many2One("reason.code", "Multi Visit Reason Code", condition=[["type", "=", "service_multi_visit"]]),
        "late_response_code_id": fields.Many2One("reason.code", "Late Response Reason Code", condition=[["type", "=", "service_late_response"]]),
        "year": fields.Char("Year", sql_function=["year", "due_date"]),
        "quarter": fields.Char("Quarter", sql_function=["quarter", "due_date"]),
        "month": fields.Char("Month", sql_function=["month", "due_date"]),
        "week": fields.Char("Week", sql_function=["week", "due_date"]),
        "activities": fields.One2Many("activity","related_id","Activities"),
        "track_id": fields.Many2One("account.track.categ","Tracking Code"),
        "track_entries": fields.One2Many("account.track.entry",None,"Tracking Entries",function="get_track_entries",function_write="write_track_entries"),
        "track_balance": fields.Decimal("Tracking Balance",function="_get_related",function_context={"path":"track_id.balance"}),
    }
    _order = "number"
    _sql_constraints = [
        ("number_uniq", "unique (number)", "The job number must be unique!"),
    ]

    def _get_number(self, context={}):
        while 1:
            num = get_model("sequence").get_number(type="job")
            if not num:
                return None
            user_id = get_active_user()
            set_active_user(1)
            res = self.search([["number", "=", num]])
            set_active_user(user_id)
            if not res:
                return num
            get_model("sequence").increment(type="job")

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            name = obj.number
            if obj.name:
                name += " - " + obj.name
            vals.append((obj.id, name))
        return vals

    _defaults = {
        "state": "planned",
        "number": _get_number,
        "request_by_id": lambda *a: get_active_user(),
        #"company_id": lambda *a: get_active_company(), # XXX: don't use this yet
        "date_open": lambda *a: time.strftime("%Y-%m-%d"),
    }

    def write(self, ids, vals, **kw):
        if vals.get("state") == "done":
            vals["date_close"] = time.strftime("%Y-%m-%d")
            for obj in self.browse(ids):
                if not obj.done_approved_by_id:
                    raise Exception("Service order has to be approved first")
        super().write(ids, vals, **kw)

    def get_total(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt_total = 0
            amt_contract = 0
            amt_job = 0
            for line in obj.lines:
                amt_total += line.amount
                if line.payment_type == "contract":
                    amt_contract += line.amount
                elif line.payment_type == "job":
                    amt_job += line.amount
            vals[obj.id] = {
                "amount_total": amt_total,
                "amount_contract": amt_contract,
                "amount_job": amt_job,
            }
        return vals

    def onchange_template(self, context={}):
        data = context["data"]
        template_id = data["template_id"]
        tmpl = get_model("job.template").browse(template_id)
        data["service_type_id"] = tmpl.service_type_id.id
        data["description"] = tmpl.description
        data["skill_level_id"] = tmpl.skill_level_id.id
        data["lines"] = []
        for line in tmpl.lines:
            line_vals = {
                "type": line.type,
                "product_id": line.product_id.id,
                "description": line.description,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price,
            }
            data["lines"].append(line_vals)
        return data

    def get_overdue(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.due_date:
                vals[obj.id] = obj.due_date < time.strftime(
                    "%Y-%m-%d") and obj.state in ("planned", "allocated", "in_progress")
            else:
                vals[obj.id] = False
        return vals

    def search_overdue(self, clause, context={}):
        return [["due_date", "<", time.strftime("%Y-%m-%d")], ["state", "in", ["planned", "allocated", "in_progress"]]]

    def copy_to_pick_out(self, ids, context={}):
        obj = self.browse(ids)[0]
        vals = {
            "type": "out",
            "contact_id": obj.contact_id.id,
            "related_id": "job,%d" % obj.id,
            "lines": [],
        }
        res = get_model("stock.location").search([["type", "=", "customer"]])
        if not res:
            raise Exception("Customer location not found")
        cust_loc_id = res[0]
        res = get_model("stock.location").search([["type", "=", "internal"]])
        if not res:
            raise Exception("Warehouse location not found")
        wh_loc_id = res[0]
        for line in obj.lines:
            prod = line.product_id
            if prod.type not in ("stock", "consumable"):
                continue
            line_vals = {
                "product_id": prod.id,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "location_from_id": prod.location_id.id or wh_loc_id,
                "location_to_id": obj.location_id.id or cust_loc_id,
            }
            vals["lines"].append(("create", line_vals))
        if not vals["lines"]:
            raise Exception("Nothing to issue")
        new_id = get_model("stock.picking").create(vals, context={"pick_type": "out"})
        pick = get_model("stock.picking").browse(new_id)
        return {
            "flash": "Goods issue %s copied from service order %s" % (pick.number, obj.number),
            "next": {
                "name": "pick_out",
                "mode": "form",
                "active_id": new_id,
            }
        }

    def copy_to_invoice(self, ids, context={}):
        obj = self.browse(ids)[0]
        inv_vals = {
            "type": "out",
            "inv_type": "invoice",
            "ref": obj.number,
            "related_id": "job,%s" % obj.id,
            "contact_id": obj.contact_id.id,
            "lines": [],
        }
        for line in obj.lines:
            if line.payment_type != "job":
                continue
            prod = line.product_id
            line_vals = {
                "product_id": prod.id,
                "description": line.description,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price,
                "account_id": prod.sale_account_id.id if prod else None,
                "tax_id": prod.sale_tax_id.id if prod else None,
                "amount": line.amount,
            }
            inv_vals["lines"].append(("create", line_vals))
        if not inv_vals["lines"]:
            raise Exception("Nothing to invoice")
        inv_id = get_model("account.invoice").create(inv_vals, {"type": "out", "inv_type": "invoice"})
        inv = get_model("account.invoice").browse(inv_id)
        return {
            "next": {
                "name": "view_invoice",
                "active_id": inv_id,
            },
            "flash": "Invoice %s created from job %s" % (inv.number, obj.number),
        }

    def onchange_product(self, context={}):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line["product_id"]
        prod = get_model("product").browse(prod_id)
        line["uom_id"] = prod.uom_id.id
        line["unit_price"] = prod.sale_price
        line["description"] = prod.description
        return data

    def onchange_due_date(self, context={}):
        print("onchange_due_date")
        data = context["data"]
        data['time_start'] = data['due_date']
        return data

    def onchange_close_date(self, context={}):
        print("onchange_close_date")
        data = context["data"]
        crr_date = time.strftime("%Y-%m-%d")
        close_date = data['close_date']
        due_date = data['due_date']
        if crr_date >= close_date:
            data['state'] = 'done'
        elif crr_date >= due_date and crr_date <= close_date:
            data['state'] = 'in_progress'
        return data

    def get_cost(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            labor_cost = 0
            for time in obj.work_time:
                labor_cost += time.amount or 0
            other_cost = 0
            for line in obj.lines:
                if line.type != "other":
                    continue
                prod = line.product_id
                other_cost += prod.cost_price or 0
            job_loc_id = obj.location_id.id
            if not job_loc_id:
                res = get_model("stock.location").search([["type", "=", "customer"]])
                if res:
                    job_loc_id = res[0]
            part_cost = 0
            for pick in obj.pickings:
                for move in pick.lines:
                    amt = move.qty * (move.unit_price or 0)
                    if move.location_to_id.id == job_loc_id and move.location_from_id.id != job_loc_id:
                        part_cost += amt
                    elif move.location_from_id.id == job_loc_id and move.location_to_id.id != job_loc_id:
                        part_cost -= amt
            vals[obj.id] = {
                "labor_cost": labor_cost,
                "part_cost": part_cost,
                "other_cost": other_cost,
                "total_cost": labor_cost + part_cost + other_cost,
            }
        return vals

    def get_sell(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            labor_sell = 0
            other_sell = 0
            part_sell = 0
            for line in obj.lines:
                if line.type == "labor":
                    labor_sell += line.amount
                elif line.type == "part":
                    part_sell += line.amount
                elif line.type == "other":
                    other_sell += line.amount
            vals[obj.id] = {
                "labor_sell": labor_sell,
                "part_sell": part_sell,
                "other_sell": other_sell,
            }
        return vals

    def approve_done(self, ids, context={}):
        if not check_permission_other("job_approve_done"):
            raise Exception("Permission denied")
        obj = self.browse(ids)[0]
        user_id = get_active_user()
        obj.write({"done_approved_by_id": user_id})
        return {
            "next": {
                "name": "job",
                "mode": "form",
                "active_id": obj.id,
            },
            "flash": "Service order completion approved successfully",
        }

    def get_days_late(self, ids, context={}):
        vals = {}
        d = datetime.now()
        for obj in self.browse(ids):
            if obj.due_date:
                vals[obj.id] = (d - datetime.strptime(obj.due_date, "%Y-%m-%d")).days
            else:
                vals[obj.id] = None
        return vals

    def get_track_entries(self,ids,context={}):
        vals={}
        for obj in self.browse(ids):
            if not obj.track_id:
                vals[obj.id]=[]
                continue
            res=get_model("account.track.entry").search([["track_id","child_of",obj.track_id.id]])
            vals[obj.id]=res
        return vals

    def write_track_entries(self,ids,field,val,context={}):
        for op in val:
            if op[0]=="create":
                rel_vals=op[1]
                for obj in self.browse(ids):
                    if not obj.track_id:
                        continue
                    rel_vals["track_id"]=obj.track_id.id
                    get_model("account.track.entry").create(rel_vals,context=context)
            elif op[0]=="write":
                rel_ids=op[1]
                rel_vals=op[2]
                get_model("account.track.entry").write(rel_ids,rel_vals,context=context)
            elif op[0]=="delete":
                rel_ids=op[1]
                get_model("account.track.entry").delete(rel_ids,context=context)

    def create_track(self,ids,context={}):
        obj=self.browse(ids[0])
        code=obj.number
        res=get_model("account.track.categ").search([["code","=",code]])
        if res:
            track_id=res[0]
        else:
            parent_id=obj.project_id.track_id.id if obj.project_id else None
            track_id=get_model("account.track.categ").create({
                    "code": code,
                    "name": code,
                    "type": "1",
                    "parent_id": parent_id,
                })
        obj.write({"track_id": track_id})
Exemple #23
0
class Move(Model):
    _name = "stock.move"
    _string = "Stock Movement"
    _name_field = "number"
    _multi_company = True
    _key = ["company_id", "number"]
    _fields = {
        "ref":
        fields.Char("Ref", search=True),  # XXX: deprecated
        "product_id":
        fields.Many2One("product", "Product", required=True, search=True),
        "location_from_id":
        fields.Many2One("stock.location",
                        "From Location",
                        required=True,
                        search=True),
        "location_to_id":
        fields.Many2One("stock.location",
                        "To Location",
                        required=True,
                        search=True),
        "qty":
        fields.Decimal("Qty", required=True, scale=6),
        "uom_id":
        fields.Many2One("uom", "UoM", required=True),
        "picking_id":
        fields.Many2One("stock.picking", "Picking", on_delete="cascade"),
        "date":
        fields.DateTime("Date", required=True, search=True),
        "cost_price_cur":
        fields.Decimal("Cost Price (Cur)", scale=6),  # in picking currency
        "cost_price":
        fields.Decimal("Cost Price", scale=6),  # in company currency
        "cost_amount":
        fields.Decimal("Cost Amount"),  # in company currency
        "cost_fixed":
        fields.Boolean("Cost Fixed"),  # don't calculate cost
        "state":
        fields.Selection([("draft", "Draft"), ("pending", "Planned"),
                          ("approved", "Approved"), ("done", "Completed"),
                          ("voided", "Voided")],
                         "Status",
                         required=True),
        "stock_count_id":
        fields.Many2One("stock.count", "Stock Count"),
        "move_id":
        fields.Many2One("account.move", "Journal Entry"),
        "user_id":
        fields.Many2One("base.user", "User"),
        "contact_id":
        fields.Many2One("contact", "Contact"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "serial_no":
        fields.Char("Serial No.", search=True),  # XXX: deprecated
        "lot_id":
        fields.Many2One("stock.lot", "Lot / Serial Number"),
        "container_from_id":
        fields.Many2One("stock.container", "From Container"),
        "container_to_id":
        fields.Many2One("stock.container", "To Container"),
        "packaging_id":
        fields.Many2One("stock.packaging", "Packaging"),
        "num_packages":
        fields.Integer("# Packages"),
        "notes":
        fields.Text("Notes"),
        "qty2":
        fields.Decimal("Qty2"),
        "company_id":
        fields.Many2One("company", "Company"),
        "invoice_id":
        fields.Many2One("account.invoice", "Invoice"),
        "related_id":
        fields.Reference(
            [["sale.order", "Sales Order"],
             ["purchase.order", "Purchase Order"], ["job", "Service Order"],
             ["account.invoice", "Invoice"], ["pawn.loan", "Loan"]],
            "Related To"),
        "number":
        fields.Char("Number", required=True, search=True),
        "journal_id":
        fields.Many2One("stock.journal", "Journal", required=True,
                        search=True),
        "alloc_costs":
        fields.One2Many("landed.cost.alloc", "move_id", "Allocated Costs"),
        "alloc_cost_amount":
        fields.Decimal("Allocated Costs",
                       scale=6,
                       function="get_alloc_cost_amount"),
        "track_id":
        fields.Many2One("account.track.categ", "Track"),
        "parent_id":
        fields.Many2One("stock.move", "Parent"),
    }
    _order = "date desc,id desc"

    def _get_loc_from(self, context={}):
        print("_get_loc_from", context)
        data = context.get("data")
        settings = get_model("settings").browse(1)
        if data:
            journal_id = data.get("journal_id")
            if journal_id:
                journal = get_model("stock.journal").browse(journal_id)
                if journal.location_from_id:
                    return journal.location_from_id.id
        pick_type = context.get("pick_type")
        if pick_type == "in":
            journal = settings.pick_in_journal_id
        elif pick_type == "out":
            journal = settings.pick_out_journal_id
        elif pick_type == "internal":
            journal = settings.pick_internal_journal_id
        else:
            journal = None
        if journal and journal.location_from_id:
            return journal.location_from_id.id
        if pick_type != "in":
            return None
        res = get_model("stock.location").search([["type", "=", "supplier"]],
                                                 order="id")
        if not res:
            return None
        return res[0]

    def _get_loc_to(self, context={}):
        print("_get_loc_to", context)
        data = context.get("data")
        settings = get_model("settings").browse(1)
        if data:
            journal_id = data.get("journal_id")
            if journal_id:
                journal = get_model("stock.journal").browse(journal_id)
                if journal.location_to_id:
                    return journal.location_to_id.id
        pick_type = context.get("pick_type")
        pick_type = context.get("pick_type")
        if pick_type == "in":
            journal = settings.pick_in_journal_id
        elif pick_type == "out":
            journal = settings.pick_out_journal_id
        elif pick_type == "internal":
            journal = settings.pick_internal_journal_id
        else:
            journal = None
        if journal and journal.location_from_id:
            return journal.location_to_id.id
        if pick_type != "out":
            return None
        res = get_model("stock.location").search([["type", "=", "customer"]])
        if not res:
            return None
        return res[0]

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence("stock_move")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id)
            res = self.search([["number", "=", num]])
            if not res:
                return num
            get_model("sequence").increment_number(seq_id)

    _defaults = {
        "state": "draft",
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "location_from_id": _get_loc_from,
        "location_to_id": _get_loc_to,
        "cost_price": 0,
        "cost_amount": 0,
        "company_id": lambda *a: get_active_company(),
        "number": _get_number,
    }

    def create(self, vals, context={}):
        pick_id = vals.get("picking_id")
        if pick_id:
            pick = get_model("stock.picking").browse(pick_id)
            vals["date"] = pick.date
            vals["picking_id"] = pick.id
            vals["journal_id"] = pick.journal_id.id
        new_id = super().create(vals, context=context)
        self.function_store([new_id])
        prod_id = vals["product_id"]
        user_id = get_active_user()
        set_active_user(1)
        get_model("product").write([prod_id], {"update_balance": True})
        set_active_user(user_id)
        return new_id

    def write(self, ids, vals, context={}):
        prod_ids = []
        for obj in self.browse(ids):
            prod_ids.append(obj.product_id.id)
        super().write(ids, vals, context=context)
        prod_id = vals.get("product_id")
        if prod_id:
            prod_ids.append(prod_id)
        self.function_store(ids)
        user_id = get_active_user()
        set_active_user(1)
        get_model("product").write(prod_ids, {"update_balance": True})
        set_active_user(user_id)

    def delete(self, ids, **kw):
        prod_ids = []
        for obj in self.browse(ids):
            prod_ids.append(obj.product_id.id)
        super().delete(ids, **kw)
        user_id = get_active_user()
        set_active_user(1)
        get_model("product").write(prod_ids, {"update_balance": True})
        set_active_user(user_id)

    def view_stock_transaction(self, ids, context={}):
        obj = self.browse(ids[0])
        if obj.picking_id:
            pick = obj.picking_id
            if pick.type == "in":
                next = {
                    "name": "pick_in",
                    "mode": "form",
                    "active_id": pick.id,
                }
            elif pick.type == "out":
                next = {
                    "name": "pick_out",
                    "mode": "form",
                    "active_id": pick.id,
                }
            elif pick.type == "internal":
                next = {
                    "name": "pick_internal",
                    "mode": "form",
                    "active_id": pick.id,
                }
        elif obj.stock_count_id:
            next = {
                "name": "stock_count",
                "mode": "form",
                "active_id": obj.stock_count_id.id,
            }
        else:
            raise Exception("Invalid stock move")
        return {"next": next}

    def set_done(self, ids, context={}):
        print("stock_move.set_done", ids)
        settings = get_model("settings").browse(1)
        prod_ids = []
        self.write(ids, {"state": "done"}, context=context)
        for obj in self.browse(ids):
            prod = obj.product_id
            prod_ids.append(prod.id)
            pick = obj.picking_id
            vals = {}
            if not obj.qty2 and prod.qty2_factor:
                qty2 = get_model("uom").convert(
                    obj.qty, obj.uom_id.id, prod.uom_id.id) * prod.qty2_factor
                vals["qty2"] = qty2
            elif prod.require_qty2 and obj.qty2 is None:
                raise Exception("Missing secondary qty for product %s" %
                                prod.code)
            if pick and pick.related_id and not obj.related_id:
                vals["related_id"] = "%s,%d" % (pick.related_id._model,
                                                pick.related_id.id)
            if pick and not pick.related_id and not obj.related_id:
                vals["related_id"] = "%s,%d" % (pick._model, pick.id)
            if obj.location_from_id.type == "view":
                raise Exception("Source location '%s' is a view location" %
                                obj.location_from_id.name)
            if obj.location_to_id.type == "view":
                raise Exception(
                    "Destination location '%s' is a view location" %
                    obj.location_to_id.name)
            if prod.require_lot and not obj.lot_id:
                raise Exception("Missing lot for product %s" % prod.code)
            if vals:
                obj.write(vals=vals, context=context)
            # change state in borrow requests # XXX: remove this
            if not obj.related_id:
                if pick.related_id._model == "product.borrow":
                    if pick.related_id.is_return_item:
                        pick.related_id.write({"state": "done"})
            elif obj.related_id._model == "product.borrow":
                if obj.related_id.is_return_item:
                    obj.related_id.write({"state": "done"})
        prod_ids = list(set(prod_ids))
        if prod_ids and settings.stock_cost_auto_compute:
            get_model("stock.compute.cost").compute_cost(
                [], context={"product_ids": prod_ids})
        if settings.stock_cost_mode == "perpetual" and not context.get(
                "no_post"):
            self.post(ids, context=context)
        self.update_lots(ids, context=context)
        self.set_reference(ids, context=context)
        self.check_periods(ids, context=context)
        print("<<<  stock_move.set_done")

    def check_periods(self, ids, context={}):
        for obj in self.browse(ids):
            d = obj.date[:10]
            res = get_model("stock.period").search([["date_from", "<=", d],
                                                    ["date_to", ">=", d],
                                                    ["state", "=", "posted"]])
            if res:
                raise Exception(
                    "Failed to validate stock movement because stock period already posted"
                )

    def set_reference(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.ref or not obj.related_id:
                continue
            ref = obj.related_id.name_get()[0][1]
            obj.write({"ref": ref})

    def reverse(self, ids, context={}):
        move_ids = []
        for obj in self.browse(ids):
            if obj.state != "done":
                raise Exception(
                    "Failed to reverse stock movement: invalid state")
            vals = {
                "journal_id":
                obj.journal_id.id,
                "product_id":
                obj.product_id.id,
                "qty":
                obj.qty,
                "uom_id":
                obj.uom_id.id,
                "location_from_id":
                obj.location_to_id.id,
                "location_to_id":
                obj.location_from_id.id,
                "cost_price_cur":
                obj.cost_price_cur,
                "cost_price":
                obj.cost_price,
                "cost_amount":
                obj.cost_amount,
                "qty2":
                obj.qty2,
                "ref":
                "Reverse: %s" % obj.ref if obj.ref else None,
                "related_id":
                "%s,%s" % (obj.related_id._model, obj.related_id.id)
                if obj.related_id else None,
                "picking_id":
                obj.picking_id.id,
            }
            move_id = self.create(vals)
            move_ids.append(move_id)
        self.set_done(move_ids)

    def get_alloc_cost_amount(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = 0
            for alloc in obj.alloc_costs:
                if alloc.landed_id.state != "posted":
                    continue
                amt += alloc.amount or 0
            vals[obj.id] = amt
        return vals

    def post(self, ids, context={}):
        print("stock.move post", ids)
        accounts = {}
        post_date = None
        pick_ids = []
        for move in self.browse(ids):
            if move.move_id:
                raise Exception(
                    "Journal entry already create for stock movement %s" %
                    move.number)
            date = move.date[:10]
            if post_date is None:
                post_date = date
            else:
                if date != post_date:
                    raise Exception(
                        "Failed to post stock movements because they have different dates"
                    )
            prod = move.product_id
            #desc="[%s] %s @ %s %s "%(prod.code,prod.name,round(move.qty,2),move.uom_id.name) # XXX: too many lines in JE
            desc = "Inventory costing"
            acc_from_id = move.location_from_id.account_id.id
            if move.location_from_id.type == "customer":
                if prod.cogs_account_id:
                    acc_from_id = prod.cogs_account_id.id
                elif prod.categ_id and prod.categ_id.cogs_account_id:
                    acc_from_id = prod.categ_id.cogs_account_id.id
            elif move.location_from_id.type == "internal":
                if prod.stock_account_id:
                    acc_from_id = prod.stock_account_id.id
                elif prod.categ_id and prod.categ_id.stock_account_id:
                    acc_from_id = prod.categ_id.stock_account_id.id
            if not acc_from_id:
                raise Exception(
                    "Missing input account for stock movement %s (date=%s, ref=%s, product=%s)"
                    % (move.id, move.date, move.ref, prod.name))
            acc_to_id = move.location_to_id.account_id.id
            if move.location_to_id.type == "customer":
                if prod.cogs_account_id:
                    acc_to_id = prod.cogs_account_id.id
                elif prod.categ_id and prod.categ_id.cogs_account_id:
                    acc_to_id = prod.categ_id.cogs_account_id.id
            elif move.location_to_id.type == "internal":
                if prod.stock_account_id:
                    acc_to_id = prod.stock_account_id.id
                elif prod.categ_id and prod.categ_id.stock_account_id:
                    acc_to_id = prod.categ_id.stock_account_id.id
            if not acc_to_id:
                raise Exception(
                    "Missing output account for stock movement %s (date=%s, ref=%s, product=%s)"
                    % (move.id, move.date, move.ref, prod.name))
            track_from_id = move.location_from_id.track_id.id
            track_to_id = move.track_id.id or move.location_to_id.track_id.id  # XXX
            amt = move.cost_amount or 0
            if not move.move_id:  # XXX: avoid create double journal entry for LC for ex
                accounts.setdefault((acc_from_id, track_from_id, desc), 0)
                accounts.setdefault((acc_to_id, track_to_id, desc), 0)
                accounts[(acc_from_id, track_from_id, desc)] -= amt
                accounts[(acc_to_id, track_to_id, desc)] += amt
            if move.picking_id:
                pick_ids.append(move.picking_id.id)
        lines = []
        for (acc_id, track_id, desc), amt in accounts.items():
            lines.append({
                "description": desc,
                "account_id": acc_id,
                "track_id": track_id,
                "debit": amt > 0 and amt or 0,
                "credit": amt < 0 and -amt or 0,
            })
        vals = {
            "narration": "Inventory costing",
            "date": post_date,
            "lines": [("create", vals) for vals in lines],
        }
        pick_ids = list(set(pick_ids))
        if len(pick_ids) == 1:
            vals["related_id"] = "stock.picking,%s" % pick_ids[0]
        move_id = get_model("account.move").create(vals)
        get_model("account.move").post([move_id])
        get_model("stock.move").write(ids, {"move_id": move_id})
        return move_id

    def to_draft(self, ids, context={}):
        move_ids = []
        for obj in self.browse(ids):
            if obj.move_id:
                move_ids.append(obj.move_id.id)
        move_ids = list(set(move_ids))
        for move in get_model("account.move").browse(move_ids):
            move.void()
            move.delete()
        self.write(ids, {"state": "draft"})
        # change state in borrow requests
        for obj in self.browse(ids):
            if obj.related_id._model == "product.borrow":
                if not obj.related_id.is_return_item:
                    obj.related_id.write({"state": "approved"})

    def update_lots(self, ids, context={}):
        for obj in self.browse(ids):
            lot = obj.lot_id
            if not lot:
                continue
            if obj.location_from_id.type != "internal" and obj.location_to_id.type == "internal":
                lot.write({"received_date": obj.date})

    # XXX
    def get_unit_price(self, ids, context={}):
        settings = get_model("settings").browse(1)
        vals = {}
        for obj in self.browse(ids):
            pick = obj.picking_id
            if pick:
                if pick.currency_rate:
                    currency_rate = pick.currency_rate
                else:
                    if pick.currency_id.id == settings.currency_id.id:
                        currency_rate = 1
                    else:
                        rate_from = pick.currency_id.get_rate(date=pick.date)
                        if not rate_from:
                            raise Exception("Missing currency rate for %s" %
                                            pick.currency_id.code)
                        rate_to = settings.currency_id.get_rate(date=pick.date)
                        if not rate_to:
                            raise Exception("Missing currency rate for %s" %
                                            settings.currency_id.code)
                        currency_rate = rate_from / rate_to
                price = obj.unit_price_cur or 0
                price_conv = get_model("currency").convert(
                    price,
                    pick.currency_id.id,
                    settings.currency_id.id,
                    rate=currency_rate)
            else:
                price_conv = None
            vals[obj.id] = price_conv
        return vals
Exemple #24
0
class LandedCostAlloc(Model):
    _name = "landed.cost.alloc"
    _fields = {
        "landed_id":
        fields.Many2One("landed.cost",
                        "Landed Cost",
                        required=True,
                        on_delete="cascade"),
        "move_id":
        fields.Many2One("stock.move", "Stock Movement", required=True),
        "date":
        fields.DateTime("Date",
                        function="_get_related",
                        function_context={"path": "move_id.date"}),
        "picking_id":
        fields.Many2One("stock.picking",
                        "Goods Receipt",
                        function="_get_related",
                        function_context={"path": "move_id.picking_id"}),
        "contact_id":
        fields.Many2One(
            "contact",
            "Contact",
            function="_get_related",
            function_context={"path": "move_id.picking_id.contact_id"}),
        "product_id":
        fields.Many2One("product",
                        "Product",
                        function="_get_related",
                        function_context={"path": "move_id.product_id"}),
        "qty":
        fields.Decimal("Qty",
                       function="_get_related",
                       function_context={"path": "move_id.qty"}),
        "uom_id":
        fields.Many2One("uom",
                        "UoM",
                        function="_get_related",
                        function_context={"path": "move_id.uom_id"}),
        "cost_price":
        fields.Decimal("Base Cost Price",
                       function="_get_related",
                       function_context={"path": "move_id.cost_price"}),
        "cost_amount":
        fields.Decimal("Base Cost Amount",
                       function="_get_related",
                       function_context={"path": "move_id.cost_amount"}),
        "location_from_id":
        fields.Many2One("stock.location",
                        "From Location",
                        function="_get_related",
                        function_context={"path": "move_id.location_from_id"}),
        "location_to_id":
        fields.Many2One("stock.location",
                        "To Location",
                        function="_get_related",
                        function_context={"path": "move_id.location_to_id"}),
        "track_id":
        fields.Many2One("account.track.categ",
                        "Track",
                        function="_get_related",
                        function_context={"path": "move_id.track_id"}),
        "qty_stock_gr":
        fields.Decimal("Qty In Stock GR",
                       function="_get_qty_stock",
                       function_multi=True),
        "qty_stock_lc":
        fields.Decimal("Qty In Stock LC",
                       function="_get_qty_stock",
                       function_multi=True),
        "est_ship":
        fields.Decimal("Est. Shipping"),
        "est_duty":
        fields.Decimal("Est. Duty"),
        "act_ship":
        fields.Decimal("Act. Shipping"),
        "act_duty":
        fields.Decimal("Act. Duty"),
        "amount":
        fields.Decimal("Total Alloc. Cost",
                       function="_get_total",
                       function_multi=True),
        "percent":
        fields.Decimal("Cost Percent",
                       function="_get_total",
                       function_multi=True),
    }

    def _get_qty_stock(self, ids, context={}):
        gr_keys = {}
        lc_keys = {}
        for obj in self.browse(ids):
            move = obj.move_id
            pick_id = move.picking_id.id
            lc_id = obj.landed_id.id
            k = (move.product_id.id, move.lot_id.id, move.location_to_id.id,
                 move.container_to_id.id)
            gr_keys.setdefault(pick_id, []).append(k)
            lc_keys.setdefault(lc_id, []).append(k)
        gr_bals = {}
        for pick_id, keys in gr_keys.items():
            pick = get_model("stock.picking").browse(pick_id)
            gr_bals[pick_id] = get_model("stock.balance").compute_key_balances(
                keys, context={"date_to": pick.date})
        lc_bals = {}
        for lc_id, keys in lc_keys.items():
            lc = get_model("landed.cost").browse(lc_id)
            lc_bals[lc_id] = get_model("stock.balance").compute_key_balances(
                keys, context={"date_to": lc.date})
        print("gr_bals", gr_bals)
        print("lc_bals", lc_bals)
        vals = {}
        for obj in self.browse(ids):
            move = obj.move_id
            pick_id = move.picking_id.id
            lc_id = obj.landed_id.id
            k = (move.product_id.id, move.lot_id.id, move.location_to_id.id,
                 move.container_to_id.id)
            qty_gr = gr_bals[pick_id][k][0]
            qty_lc = lc_bals[lc_id][k][0]
            vals[obj.id] = {
                "qty_stock_gr": qty_gr,
                "qty_stock_lc": qty_lc,
            }
        return vals

    def _get_total(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = 0
            amt += obj.est_ship or 0
            amt += obj.est_duty or 0
            amt += obj.act_ship or 0
            amt += obj.act_duty or 0
            vals[obj.id] = {
                "amount":
                amt,
                "percent":
                amt * 100 / obj.cost_amount if obj.cost_amount else None,
            }
        return vals
Exemple #25
0
class Product(Model):
    _name = "product"
    _string = "Product"
    _audit_log = True
    _key = ["code", "state", "company_id"]
    _order = "code,name"
    _export_name_field = "code"
    _history = True
    _fields = {
        "name":
        fields.Char("Name",
                    required=True,
                    search=True,
                    translate=True,
                    size=256),
        "code":
        fields.Char("Code", required=True, search=True),
        "type":
        fields.Selection([["stock", "Stockable"], ["consumable", "Consumable"],
                          ["service", "Service"], ["master", "Master"],
                          ["bundle", "Bundle"]],
                         "Product Type",
                         required=True,
                         search=True),
        "uom_id":
        fields.Many2One("uom", "Default UoM", required=True, search=True),
        "parent_id":
        fields.Many2One("product", "Master Product"),
        "categ_id":
        fields.Many2One("product.categ", "Product Category", search=True),
        "description":
        fields.Text("Description", translate=True),
        "purchase_price":
        fields.Decimal("Purchase Price", scale=6),
        "sale_price":
        fields.Decimal("List Price (Sales Invoice UoM)", scale=6),
        "sale_price_order_uom":
        fields.Decimal("List Price (Sales Order UoM)",
                       scale=6,
                       function="get_sale_price_order_uom"),
        "tags":
        fields.Many2Many("tag", "Tags"),
        "image":
        fields.File("Image"),
        "cost_method":
        fields.Selection(
            [["standard", "Standard Cost"], ["average", "Weighted Average"],
             ["fifo", "FIFO"], ["lifo", "LIFO"]], "Costing Method"),
        "cost_price":
        fields.Decimal("Cost Price", scale=6),
        "stock_in_account_id":
        fields.Many2One("account.account",
                        "Stock Input Account",
                        multi_company=True),  # XXX: deprecated
        "stock_out_account_id":
        fields.Many2One("account.account",
                        "Stock Output Account",
                        multi_company=True),  # XXX: deprecated
        "cogs_account_id":
        fields.Many2One("account.account",
                        "Cost Of Goods Sold Account",
                        multi_company=True),
        "stock_account_id":
        fields.Many2One("account.account",
                        "Inventory Account",
                        multi_company=True),
        "purchase_account_id":
        fields.Many2One("account.account",
                        "Purchase Account",
                        multi_company=True),
        "purchase_tax_id":
        fields.Many2One("account.tax.rate", "Purchase Tax"),
        "supplier_id":
        fields.Many2One("contact", "Default Supplier"),  # XXX: deprecated
        "sale_account_id":
        fields.Many2One("account.account", "Sales Account",
                        multi_company=True),
        "sale_return_account_id":
        fields.Many2One("account.account",
                        "Sales Returns Account",
                        multi_company=True),
        "sale_promotion_account_id":
        fields.Many2One("account.account",
                        "Sales Promotion Account",
                        multi_company=True),
        "sale_tax_id":
        fields.Many2One("account.tax.rate", "Sales Tax"),
        "sale_promotion_tax_id":
        fields.Many2One("account.tax.rate", "Promotion Tax"),
        "location_id":
        fields.Many2One("stock.location", "Warehouse"),  # XXX: deprecated
        "bin_location":
        fields.Char("Bin Location"),
        "update_balance":
        fields.Boolean("Update Balance"),
        "active":
        fields.Boolean("Active"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "categs":
        fields.Many2Many("product.categ",
                         "Other Categories"),  # XXX: deprecated
        "attributes":
        fields.One2Many("product.attribute.value", "product_id", "Attributes"),
        "variants":
        fields.One2Many("product", "parent_id", "Variants"),
        #"variant_values": fields.One2Many("product.custom.option.variant.value","product_id","Variant Values"),
        "custom_options":
        fields.Many2Many("product.custom.option", "Custom Options"),
        "images":
        fields.One2Many("product.image", "product_id", "Images"),
        "store_type_id":
        fields.Many2One("store.type", "Storage Type"),
        "brand_id":
        fields.Many2One("product.brand", "Brand", search=True),
        "related_products":
        fields.Many2Many("product",
                         "Related Products",
                         relfield="product1_id",
                         relfield_other="product2_id"),
        "min_sale_qty":
        fields.Decimal("Min Sale Qty"),
        "sale_unit_qty":
        fields.Decimal("Sale Unit Qty"),
        "shelf_life":
        fields.Decimal("Shelf Life (Days)"),
        "weight":
        fields.Decimal("Weight (Kg)"),
        "volume":
        fields.Decimal("Volume (M^3)"),
        "width":
        fields.Decimal("Width"),
        "height":
        fields.Decimal("Height"),
        "length":
        fields.Decimal("Length"),
        "packing_size":
        fields.Char("Packing Size"),
        "details":
        fields.Text("Product Details", translate=True),
        "details2":
        fields.Text("Product Details (2)", translate=True),
        "details2_label":
        fields.Char("Product Details Label (2)"),
        "details3":
        fields.Text("Product Details (3)", translate=True),
        "details3_label":
        fields.Char("Product Details Label (3)"),
        "other_url":
        fields.Char("Product URL", size=256),
        "purchase_currency_id":
        fields.Many2One("currency", "Purchase Currency"),
        "purchase_currency_rate":
        fields.Decimal("Purchase Currency Rate", scale=6),
        "purchase_duty_percent":
        fields.Decimal("Import Duty (%)"),
        "purchase_ship_percent":
        fields.Decimal("Shipping Charge (%)"),
        "landed_cost":
        fields.Decimal("Landed Cost",
                       function="get_landed_cost",
                       function_multi=True),
        "landed_cost_conv":
        fields.Decimal("Landed Cost (Conv)",
                       function="get_landed_cost",
                       function_multi=True),
        "gross_profit":
        fields.Decimal("Gross Profit (%)"),
        "auto_list_price":
        fields.Decimal("Auto List Price", function="get_auto_list_price"),
        "max_discount":
        fields.Decimal("Max Discount (%)", function="get_max_discount"),
        "price_index":
        fields.Decimal("Price Index", function="get_price_index"),
        "price_notes":
        fields.Text("Notes"),
        "price_date":
        fields.Date("Price Date"),
        "pricelist_items":
        fields.One2Many("price.list.item", "product_id", "Pricelist Items"),
        "procure_method":
        fields.Selection([["mto", "Make To Order"], ["mts", "Make To Stock"]],
                         "Procurement Method"),
        "supply_method":
        fields.Selection(
            [["purchase", "Purchase"], ["production", "Production"]],
            "Supply Method"),
        "can_sell":
        fields.Boolean("Can Sell"),
        "can_purchase":
        fields.Boolean("Can Purchase"),
        "id":
        fields.Integer("Database ID", readonly=True),  # MTS
        "create_time":
        fields.DateTime("Create Time", readonly=True),  # MTS
        "supplier_product_code":
        fields.Char("Supplier Product Code"),  # XXX: deprecated
        "require_qty2":
        fields.Boolean("Require Secondary Qty"),
        "qty2_factor":
        fields.Decimal("UoM -> Secondary Qty Factor", scale=6),
        "replacements":
        fields.Many2Many("product",
                         "Replacement Products",
                         reltable="m2m_product_replacement",
                         relfield="product1_id",
                         relfield_other="product2_id"),
        "suppliers":
        fields.One2Many("product.supplier", "product_id", "Suppliers"),
        "max_qty_loss":
        fields.Decimal("Max Qty Loss"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "ship_methods":
        fields.Many2Many("ship.method", "Shipping Methods"),
        "ecom_discount_percent":
        fields.Decimal("Ecom. Discount Percent"),  # XXX: deprecated
        "ecom_special_price":
        fields.Decimal("Ecom. Special Price"),  # XXX: deprecated
        #"ecom_sale_price": fields.Decimal("Ecom. Sale Price", function="get_ecom_sale_price", function_multi=True), # XXX: deprecated
        #"ecom_has_discount": fields.Decimal("Ecom. Has Discount", function="get_ecom_sale_price", function_multi=True), # XXX: deprecated
        "variant_attributes":
        fields.Json("Variant Attributes", function="get_variant_attributes"),
        "company_id":
        fields.Many2One("company", "Company"),
        "sale_channels":
        fields.Many2Many("sale.channel", "Sales Channels"),
        "customer_price":
        fields.Decimal("Customer Price",
                       function="get_customer_price",
                       function_multi=True),
        "customer_has_discount":
        fields.Decimal("Customer Has Discount",
                       function="get_customer_price",
                       function_multi=True),
        "customer_discount_text":
        fields.Decimal("Customer Discount Text",
                       function="get_customer_price",
                       function_multi=True),
        "customer_discount_percent":
        fields.Decimal("Customer Discount Percent",
                       function="get_customer_price",
                       function_multi=True),
        "sale_company_id":
        fields.Many2One("company", "Sold By"),
        "groups":
        fields.Many2Many("product.group", "Groups"),
        "payment_methods":
        fields.Many2Many("payment.method", "Payment Methods"),
        "locations":
        fields.One2Many("product.location", "product_id", "Warehouses"),
        "stock_qty":
        fields.Decimal("Total Stock Qty", function="get_stock_qty"),
        "stock_lots":
        fields.Many2Many("stock.lot",
                         "Lots In Stock",
                         function="get_stock_lots"),
        "state":
        fields.Selection([["draft", "Draft"], ["approved", "Approved"]],
                         "Status"),
        "sale_uom_id":
        fields.Many2One("uom", "Sales Order UoM"),
        "sale_invoice_uom_id":
        fields.Many2One("uom", "Sales Invoice UoM"),
        "sale_to_stock_uom_factor":
        fields.Decimal("Sales Order -> Stock Uom Conversion Factor", scale=6),
        "sale_to_invoice_uom_factor":
        fields.Decimal("Sales Order -> Sales Invoice Uom Conversion Factor",
                       scale=6),
        "purchase_uom_id":
        fields.Many2One("uom", "Purchase Order UoM"),
        "purchase_invoice_uom_id":
        fields.Many2One("uom", "Purchase Invoice UoM"),
        "purchase_to_stock_uom_factor":
        fields.Decimal("Purchase Order -> Stock Uom Conversion Factor",
                       scale=6),
        "purchase_to_invoice_uom_factor":
        fields.Decimal(
            "Purchase Order -> Purchase Invoice Uom Conversion Factor",
            scale=6),
        "purchase_lead_time":
        fields.Integer("Purchasing Lead Time (Days)"),
        "purchase_min_qty":
        fields.Decimal("Purchase Minimum Qty"),
        "purchase_qty_multiple":
        fields.Decimal("Purchase Qty Multiple"),
        "mfg_lead_time":
        fields.Integer("Manufacturing Lead Time (Days)"),
        "mfg_min_qty":
        fields.Decimal("Manufacturing Minimum Qty"),
        "mfg_qty_multiple":
        fields.Decimal("Manufacturing Qty Multiple"),
        #"purchase_price_uom_id": fields.Many2One("uom", "Purchase Price UoM"), # not needed?
        #"sale_price_uom_id": fields.Many2One("uom", "List Price UoM"), # not needed?
        "events":
        fields.Many2Many("sale.event", "Events"),
        "is_published":
        fields.Boolean("Publish Product"),
        "require_lot":
        fields.Boolean("Require Lot"),
        "lot_select":
        fields.Selection([["fifo", "FIFO"], ["fefo", "FEFO"]],
                         "Lot Selection"),
        "components":
        fields.One2Many("product.component", "product_id",
                        "Bundle Components"),
        "approve_date":
        fields.DateTime("Approve Date"),
        "service_items":
        fields.One2Many("service.item", "product_id", "Service Items"),
        "lots":
        fields.One2Many("stock.lot", "product_id", "Lots"),
        "stock_plan_horizon":
        fields.Integer("Inventory Planning Horizon (days)"),  # XXX: deprecated
        "ecom_hide_qty":
        fields.Boolean("Hide Stock Qty From Website"),
        "ecom_hide_unavail":
        fields.Boolean("Hide From Website When Out Of Stock"),
        "ecom_no_order_unavail":
        fields.Boolean("Prevent Orders When Out Of Stock"),
        "ecom_select_lot":
        fields.Boolean("Customers Can Select Lot When Ordering"),
        "product_origin":
        fields.Char("Product Origin"),
        "stock_balances":
        fields.One2Many("stock.balance", "product_id", "Stock Balances"),
        "check_lot_neg_stock":
        fields.Boolean("Check Lot Negative Stock"),
        "sale_lead_time_nostock":
        fields.Integer("Sale Lead Time When Out Of Stock (Days)"),
        "ship_methods":
        fields.Many2Many("ship.method", "Shipping Methods"),
        "delivery_weekdays":
        fields.Char("Delivery Weekday Constraints"),
    }

    _defaults = {
        "update_balance": True,
        "active": True,
        "can_sell": False,
        "can_purchase": False,
        "company_id": lambda *a: access.get_active_company(),
        "state": "draft",
    }

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            if obj.code:
                name = "[%s] %s" % (obj.code, obj.name)
            else:
                name = obj.name
            vals.append((obj.id, name, obj.image))
        return vals

    def name_search(self, name, condition=None, context={}, limit=None, **kw):
        print("condition", condition)
        search_mode = context.get("search_mode")
        print("##############################")
        print("search_mode", search_mode)
        if search_mode == "suffix":
            cond = [["code", "=ilike", "%" + name]]
        elif search_mode == "prefix":
            cond = [["code", "=ilike", name + "%"]]
        else:
            cond = [["code", "ilike", name]]
        if condition:
            cond = [cond, condition]
        ids1 = self.search(cond, limit=limit)
        if search_mode == "suffix":
            cond = [["name", "=ilike", "%" + name]]
        elif search_mode == "prefix":
            cond = [["name", "=ilike", name + "%"]]
        else:
            cond = [["name", "ilike", name]]
        if condition:
            cond = [cond, condition]
        ids2 = self.search(cond, limit=limit)
        ids = list(set(ids1 + ids2))
        return self.name_get(ids, context=context)

    def copy(self, ids, context={}):
        replace_id = context.get("replace_id")
        obj = self.browse(ids)[0]
        code = obj.code
        if not replace_id:
            for i in range(1, 10):
                code = obj.code + " (%s)" % i
                res = self.search([["code", "=", code]])
                if not res:
                    break
        vals = {
            "name": obj.name,
            "code": code,
            "type": obj.type,
            "uom_id": obj.uom_id.id,
            #"parent_id": obj.parent_id.id, XXX
            "description": obj.description,
            "image": obj.image,
            "categ_id": obj.categ_id.id,
            "categs": [("set", [c.id for c in obj.categs])],
            "supply_method": obj.supply_method,
            "procure_method": obj.procure_method,
            "can_sell": obj.can_sell,
            "can_purchase": obj.can_purchase,
            "sale_uom_id": obj.sale_uom_id.id,
            "sale_invoice_uom_id": obj.sale_invoice_uom_id.id,
            "sale_to_stock_uom_factor": obj.sale_to_stock_uom_factor,
            "sale_to_invoice_uom_factor": obj.sale_to_invoice_uom_factor,
            "purchase_uom_id": obj.purchase_uom_id.id,
            "purchase_invoice_uom_id": obj.purchase_invoice_uom_id.id,
            "purchase_to_stock_uom_factor": obj.purchase_to_stock_uom_factor,
            "purchase_to_invoice_uom_factor":
            obj.purchase_to_invoice_uom_factor,
            "purchase_price": obj.purchase_price,
            "purchase_account_id": obj.purchase_account_id.id,
            "purchase_tax_id": obj.purchase_tax_id.id,
            "supplier_id": obj.supplier_id.id,
            "sale_price": obj.sale_price,
            "sale_account_id": obj.sale_account_id.id,
            "sale_tax_id": obj.sale_tax_id.id,
            "sale_return_account_id": obj.sale_return_account_id.id,
            "sale_promotion_account_id": obj.sale_promotion_account_id.id,
            "sale_promotion_tax_id": obj.sale_promotion_tax_id.id,
            "cost_method": obj.cost_method,
            "stock_in_account_id": obj.stock_in_account_id.id,
            "stock_out_account_id": obj.stock_out_account_id.id,
            "bin_location": obj.bin_location,
            "sale_company_id": obj.sale_company_id.id,
            "attributes": [],
        }
        vals["attributes"] = [("delete_all", )]
        for attr in obj.attributes:
            vals["attributes"].append(("create", {
                "attribute_id": attr.attribute_id.id,
                "option_id": attr.option_id.id
            }))
        vals["pricelist_items"] = [("delete_all", )]
        for item in obj.pricelist_items:
            vals["pricelist_items"].append(("create", {
                "list_id": item.list_id.id,
                "price": item.price
            }))
        vals["images"] = [("delete_all", )]
        for image in obj.images:
            vals["images"].append(("create", {
                "image": image.image,
                "title": image.title
            }))
        print("vals", vals)
        if replace_id:
            self.write([replace_id], vals)
            new_id = replace_id
        else:
            new_id = self.create(vals)
        return {
            "next": {
                "name": "product",
                "mode": "form",
                "active_id": new_id,
            },
            "flash": "New product copied from %s" % obj.name,
        }

    def get_landed_cost(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = Decimal(obj.purchase_price or 0) * Decimal(
                1 + (obj.purchase_duty_percent or 0) /
                100) * Decimal(1 + (obj.purchase_ship_percent or 0) / 100)
            amt_cur = amt * Decimal(obj.purchase_currency_rate or 1)
            vals[obj.id] = {
                "landed_cost": amt,
                "landed_cost_conv": amt_cur,
            }
        return vals

    def get_auto_list_price(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.landed_cost_conv and obj.gross_profit and obj.gross_profit != 100:
                vals[obj.id] = obj.landed_cost_conv / (1 -
                                                       obj.gross_profit / 100)
            else:
                vals[obj.id] = None
        return vals

    def get_max_discount(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.sale_price and obj.landed_cost_conv:
                vals[obj.id] = max(0, (obj.sale_price - obj.landed_cost_conv) /
                                   obj.sale_price * 100)
            else:
                vals[obj.id] = None
        return vals

    def get_price_index(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.sale_price and obj.landed_cost_conv:
                vals[obj.id] = obj.sale_price / obj.landed_cost_conv
            else:
                vals[obj.id] = None
        return vals

    def update_prices(self, context={}):
        print("update_prices")
        data = context["data"]
        purchase_price = data.get("purchase_price")
        purchase_currency_rate = data.get("purchase_currency_rate")
        purchase_duty_percent = data.get("purchase_duty_percent")
        purchase_ship_percent = data.get("purchase_ship_percent")
        if purchase_price:
            landed_cost = purchase_price * \
                (1 + (purchase_duty_percent or 0) / 100) * (1 + (purchase_ship_percent or 0) / 100)
            landed_cost_conv = landed_cost * (purchase_currency_rate or 1)
        else:
            landed_cost = None
            landed_cost_conv = None
        gross_profit = data.get("gross_profit")
        if landed_cost_conv and gross_profit and gross_profit != 100:
            auto_list_price = landed_cost_conv / (1 - gross_profit / 100)
        else:
            auto_list_price = None
        sale_price = data.get("sale_price")
        if sale_price and landed_cost_conv:
            max_discount = max(0, (sale_price - landed_cost_conv) /
                               sale_price * 100)
            price_index = sale_price / landed_cost
        else:
            max_discount = None
            price_index = None
        data.update({
            "landed_cost": landed_cost,
            "landed_cost_conv": landed_cost_conv,
            "auto_list_price": auto_list_price,
            "max_discount": max_discount,
            "price_index": price_index,
        })
        return data

    def get_ecom_sale_price(self, ids, context={}):
        raise Exception("Deprecated!")
        vals = {}
        for obj in self.browse(ids):
            if obj.ecom_discount_percent:
                p = (obj.sale_price
                     or 0) * (1 - obj.ecom_discount_percent / 100)
                has_disc = True
            elif obj.ecom_special_price:
                p = obj.ecom_special_price
                has_disc = True
            else:
                p = obj.sale_price or 0
                has_disc = False
            vals[obj.id] = {
                "ecom_sale_price": p,
                "ecom_has_discount": has_disc,
            }
        return vals

    def create_variants(self, ids, context={}):  # deprecated
        print("##################################")
        print("product.create_variants", ids)
        obj = self.browse(ids[0])
        if obj.type != "master":
            raise Exception("Not a master product")
        if not obj.custom_options:
            raise Exception("No custom options for this product")
        variants = [{}]
        for opt in obj.custom_options:
            new_variants = []
            for variant in variants:
                for opt_val in opt.values:
                    new_variant = variant.copy()
                    new_variant[opt.code] = opt_val.code
                    new_variants.append(new_variant)
            variants = new_variants
        print("variants", len(variants), variants)
        count = 1
        for variant in variants:
            vals = {
                "code": "%s_VARIANT_%.2d" % (obj.code, count),
                "name": obj.name,
                "type": "stock",
                "uom_id": obj.uom_id.id,
                "parent_id": obj.id,
                "location_id": obj.location_id.id,
                "attributes": [],
            }
            for k, v in variant.items():
                name = "_CUST_OPT_" + k
                res = get_model("product.attribute").search(
                    [["name", "=", name]])
                if res:
                    attr_id = res[0]
                else:
                    attr_id = get_model("product.attribute").create(
                        {"name": name})
                vals["attributes"].append(("create", {
                    "attribute_id": attr_id,
                    "value": v,
                }))
            get_model("product").create(vals)
            count += 1
        return {
            "flash": "%d variants created" % len(variants),
        }

    def get_variant_attributes(self, ids, context={}):
        print("get_variant_attributes", ids)
        vals = {}
        for obj in self.browse(ids):
            attrs = []
            attr_options = {}
            for variant in obj.variants:
                for attr in variant.attributes:
                    if not attr.attribute_id or not attr.option_id:
                        continue
                    attr_code = attr.attribute_id.code
                    attr_name = attr.attribute_id.name
                    attrs.append((attr_name, attr_code))
                    opt_code = attr.option_id.code
                    opt_name = attr.option_id.name
                    opt_sequence = attr.option_id.sequence
                    if attr_code == "color":
                        opt_image = variant.image or attr.option_id.image
                    else:
                        opt_image = attr.option_id.image
                    #attr_options.setdefault(attr_code,[]).append((opt_sequence,opt_name,opt_code,opt_image))
                    attr_options.setdefault(attr_code, []).append(
                        (opt_sequence, opt_name, opt_code))
            attrs = list(set(attrs))
            res = []
            for attr_name, attr_code in sorted(attrs):
                attr_vals = {
                    "code": attr_code,
                    "name": attr_name,
                    "values": [],
                }
                attr_options[attr_code] = list(set(attr_options[attr_code]))
                #for opt_sequence,opt_name,opt_code,opt_image in sorted(attr_options[attr_code]):
                for opt_sequence, opt_name, opt_code in sorted(
                        attr_options[attr_code]):
                    attr_vals["values"].append({
                        "sequence": opt_sequence,
                        "code": opt_code,
                        "name": opt_name,
                        #"image": opt_image,
                    })
                res.append(attr_vals)
            vals[obj.id] = res
        print("vals", vals)
        return vals

    def get_customer_price(self, ids, context={}):  # XXX: make it faster
        pricelist_id = context.get("pricelist_id")
        pricelist_ids = context.get("pricelist_ids")
        if pricelist_ids is None and pricelist_id:
            pricelist_ids = [pricelist_id]
        vals = {}
        for obj in self.browse(ids):
            sale_price = None
            discount_text = None
            discount_percent = None
            if pricelist_ids:
                min_sale_price = None
                for item in obj.pricelist_items:
                    if item.list_id.id in pricelist_ids:
                        sale_price = (item.price or 0)
                        if min_sale_price is None or sale_price < min_sale_price:
                            min_sale_price = sale_price
                            discount_text = item.discount_text
                            discount_percent = item.discount_percent
                sale_price = min_sale_price
            if sale_price is None:
                sale_price = (obj.sale_price or 0)
            has_discount = sale_price < (obj.sale_price or 0)
            vals[obj.id] = {
                "customer_price": sale_price,
                "customer_has_discount": has_discount,
                "customer_discount_text": discount_text,
                "customer_discount_percent": discount_percent,
            }
        return vals

    def get_stock_qty(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            qty = 0
            for loc in obj.locations:
                qty += loc.stock_qty
            vals[obj.id] = qty
        return vals

    def get_stock_lots(self, ids, context={}):
        db = database.get_connection()
        res = db.query(
            "SELECT b.product_id,b.location_id,p.sale_price,l.id AS lot_id,l.number,l.weight,SUM(b.qty_virt) AS qty FROM stock_balance b JOIN stock_lot l ON l.id=b.lot_id JOIN product p ON p.id=b.product_id WHERE b.product_id IN %s GROUP BY b.product_id,b.location_id,p.sale_price,l.id,l.number,l.weight ORDER BY l.expiry_date,l.received_date",
            tuple(ids))
        lots = {}
        prod_lots = {}
        for r in res:
            k = (r.product_id, r.lot_id)
            if k not in lots:
                lot_vals = {
                    "id": r.lot_id,
                    "qty": 0,
                }
                lots[k] = lot_vals
                prod_lots.setdefault(r.product_id, []).append(lot_vals)
            lots[k]["qty"] += r.qty
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = [
                l["id"] for l in prod_lots.get(obj.id, []) if l["qty"] > 0
            ]
        return vals

    def to_draft(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "draft", "is_published": False})  #XXX

    def approve(self, ids, context={}):
        for obj in self.browse(ids):
            res = self.search([["code", "=", obj.code],
                               ["state", "=", "approved"],
                               ["company_id", "=", obj.company_id.id]])
            if res:
                repl_id = res[0]
                obj.copy(context={"replace_id": repl_id})
                obj.write({"active": False})
            else:
                vals = {
                    "state": "approved",
                }
                if not obj.parent_id:
                    group_ids = []  #XXX
                    for group in obj.groups:
                        group_ids.append(group.id)
                    res = get_model("product.group").search(
                        [["code", "=", "new"]])
                    if res and res[0] not in group_ids:
                        group_ids.append(res[0])
                    vals.update({
                        "is_published": True,
                        "groups": [("set", group_ids)]
                    })
                if not obj.approve_date:
                    t = time.strftime("%Y-%m-%d %H:%M:%S")
                    vals.update({"approve_date": t})
                obj.write(vals)
        return {
            "flash": "Products approved",
        }

    def ecom_preview(self, ids, context={}):
        prod_id = ids[0]
        return {
            "next": {
                "type": "url",
                "url": "/ecom_product?product_id=%s" % prod_id,
            }
        }

    def get_sale_price_order_uom(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            factor = obj.sale_to_invoice_uom_factor or 1
            vals[obj.id] = math.ceil((obj.sale_price or 0) * factor)
        return vals

    def create_thumbnails(self, ids, context={}):
        print("Product.create_thumbnails", ids)
        for obj in self.browse(ids):
            if not obj.image:
                continue
            dbname = database.get_active_db()
            if not dbname:
                return None
            fdir = os.path.join(os.getcwd(), "static", "db", dbname, "files")
            path = os.path.join(fdir, obj.image)
            basename, ext = os.path.splitext(obj.image)
            res = "," in basename
            if not res:
                rand = base64.urlsafe_b64encode(os.urandom(8)).decode()
                res = os.path.splitext(obj.image)
                basename, ext = res
                fname2 = basename + "," + rand + ext
                #rename image
                dest_path = fdir + "/" + fname2
                print("destination path and file name ", dest_path)
                cmd = "cp %s %s" % (path, dest_path)
                os.system(cmd)
                obj.write({
                    'image': fname2,
                })
                utils.create_thumbnails(fname2)
            else:
                print("called", obj.image)
                utils.create_thumbnails(obj.image)
Exemple #26
0
class EmailMessage(Model):
    _name = "email.message"
    _string = "Email Message"
    _name_field = "message_id"
    _fields = {
        "type":
        fields.Selection([["in", "Incoming"], ["out", "Outgoing"]],
                         "Type"),  # XXX: deprecated
        "date":
        fields.DateTime("Date", required=True, search=True, readonly=True),
        "from_addr":
        fields.Char("From", required=True, search=True, size=256),
        "to_addrs":
        fields.Char("To", size=256, search=True),
        "cc_addrs":
        fields.Char("Cc", size=256, search=True),
        "subject":
        fields.Char("Subject", required=True, size=128, search=True),
        # XXX: deprecated
        "content_type":
        fields.Selection([["plain", "Plain Text"], ["html", "HTML"]],
                         "Content Type"),
        "body":
        fields.Text("Body", search=True),
        "state":
        fields.Selection([["draft", "Draft"], ["to_send", "To Send"],
                          ["sent", "Sent"], ["delivered", "Delivered"],
                          ["bounced", "Bounced"], ["rejected", "Rejected"],
                          ["received", "Received"], ["error", "Error"]],
                         "Status",
                         required=True,
                         search=True),
        "message_id":
        fields.Char("Message ID", size=256),
        "mailbox_message_uid":
        fields.Char("Mailbox Message UID"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "name_id":
        fields.Reference([["contact", "Contact"], ["sale.lead", "Sales Lead"],
                          ["mkt.target", "Marketing Target"]],
                         "Contact Person",
                         search=True),
        "related_id":
        fields.Reference(
            [["sale.opportunity", "Sales Opportunity"],
             ["sale.quot", "Quotation"], ["sale.order", "Sales Order"],
             ["job", "Service Order"], ["purchase.order", "Purchase Order"],
             ["account.invoice", "Invoice"],
             ["mkt.campaign", "Marketing Campaign"],
             ["ecom.cart", "Ecommerce Cart"]], "Related To"),
        "attachments":
        fields.One2Many("email.attach", "email_id", "Attachments"),
        "events":
        fields.One2Many("email.event", "email_id", "Email Events"),
        "opened":
        fields.Boolean("Opened", readonly=True, search=True),
        "clicked":
        fields.Boolean("Link Clicked", readonly=True, search=True),
        "in_reject_list":
        fields.Boolean("Black Listed", function="check_reject_list"),
        "parent_uid":
        fields.Char("Parent Unique ID", readonly=True, size=256),  # XXX
        "parent_id":
        fields.Many2One("email.message", "Parent"),
        "mailbox_id":
        fields.Many2One("email.mailbox", "Mailbox", required=True,
                        search=True),
        "open_detect":
        fields.Boolean("Open Detect", function="get_open_detect"),
        "num_attach":
        fields.Integer("# Attach.", function="get_num_attach"),
        "error_message":
        fields.Text("Error Message"),
        "template_id":
        fields.Many2One("email.template", "Email Template"),
    }
    _order = "date desc"

    def _get_mailbox(self, context={}):
        user_id = get_active_user()
        res = get_model("email.mailbox").search([["type", "=", "out"]],
                                                order="id")
        if not res:
            return None
        return res[0]

    def _get_from_addr(self, context={}):
        user_id = get_active_user()
        user = get_model("base.user").browse(user_id)
        return user.email

    def _get_to_addrs(self, context={}):
        defaults = context.get("defaults")
        if not defaults:
            return None
        parent_id = int(defaults.get("parent_id"))
        if not parent_id:
            return None
        parent = get_model("email.message").browse(parent_id)
        return parent.from_addr

    def _get_subject(self, context={}):
        defaults = context.get("defaults")
        if not defaults:
            return None
        parent_id = int(defaults.get("parent_id"))
        if not parent_id:
            return None
        parent = get_model("email.message").browse(parent_id)
        s = parent.subject
        return "Re: " + s

    def _get_body(self, context={}):
        defaults = context.get("defaults")
        if not defaults:
            return None
        parent_id = int(defaults.get("parent_id"))
        if not parent_id:
            return None
        parent = get_model("email.message").browse(parent_id)
        d = datetime.strptime(parent.date, "%Y-%m-%d %H:%M:%S")
        pbody = parent.body
        m = re.search("<body>(.*)</body>", pbody, re.DOTALL)
        if m:
            pbody = m.group(1)
        m = re.search("<BODY>(.*)</BODY>", pbody, re.DOTALL)
        if m:
            pbody = m.group(1)
        body = "<div>On %s, %s wrote:" % (d.strftime("%a, %b %d, %Y at %H:%M"),
                                          parent.from_addr)
        body += '<blockquote style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">' + \
            pbody + "</blockquote>"
        body += "</div>"
        return body

    _defaults = {
        "type": "out",
        "state": "draft",
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "mailbox_id": _get_mailbox,
        "from_addr": _get_from_addr,
        "to_addrs": _get_to_addrs,
        "subject": _get_subject,
        "body": _get_body,
    }

    def to_send(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "to_send"})

    def to_draft(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "draft"})

    def send_emails(self, context={}):  # FIXME
        print("send_emails")
        mailbox_ids = get_model("email.mailbox").search(
            [["account_id.type", "in", ["smtp", "mailgun"]]])
        get_model("email.mailbox").send_emails(mailbox_ids)

    def check_sent_emails(self, context={}):
        print("send_sent_emails")
        res = get_model("email.account").search([["type", "=",
                                                  "mailgun"]])  # XXX
        if not res:
            return
        acc_id = res[0]
        get_model("email.account").check_sent_emails([acc_id])

    def send_from_template(self,
                           template=None,
                           from_addr=None,
                           to_addrs=None,
                           context={}):
        if not template:
            raise Exception("Missing template")
        res = get_model("email.template").search([["name", "=", template]])
        if not res:
            raise Exception("Template not found: %s" % template)
        tmpl_id = res[0]
        tmpl = get_model("email.template").browse(tmpl_id)
        trigger_model = context.get("trigger_model")
        if not trigger_model:
            raise Exception("Missing trigger model")
        tm = get_model(trigger_model)
        trigger_ids = context.get("trigger_ids")
        if trigger_ids is None:
            raise Exception("Missing trigger ids")
        user_id = get_active_user()
        if user_id:
            user = get_model("base.user").browse(user_id)
        else:
            user = None
        for obj in tm.browse(trigger_ids):
            tmpl_ctx = {"obj": obj, "user": user, "context": context}
            get_model("email.template").create_email([tmpl_id], tmpl_ctx)

    def import_email(self, vals):
        print("import_email from=%s to=%s cc=%s subject=%s" %
              (vals["from_addr"], vals.get("to_addrs"), vals.get("cc_addrs"),
               vals["subject"]))

        def _check_addr(addrs):
            if not addrs:
                return False
            addr_list = addrs.split(",")
            for addr in addr_list:
                addr = addr.strip()
                res = get_model("base.user").search([["email", "=ilike",
                                                      addr]])
                if res:
                    return True
                res = get_model("contact").search([["email", "=ilike", addr]])
                if res:
                    return True
                res = get_model("sale.lead").search([["email", "=ilike",
                                                      addr]])
                if res:
                    return True
            return False

        if not _check_addr(vals["from_addr"]):
            print_color(
                "email ignored: unknown 'from' address: %s" %
                vals["from_addr"], "red")
            return None
        if not _check_addr(vals.get("to_addrs")) and not _check_addr(
                vals.get("cc_addrs")):
            print_color(
                "email ignored: unknown 'to' and 'cc' address: to=%s cc=%s" %
                (vals.get("to_addrs"), vals.get("cc_addrs")), "red")
            return None
        new_id = self.create(vals)
        print_color("email imported: id=%d" % new_id, "green")
        self.link_emails([new_id])
        self.trigger([new_id], "received")
        return new_id

    def link_emails(self, ids, context={}):
        def _get_addr_contact(addr):
            res = get_model("contact").search([["email", "=ilike", addr]])
            if res:
                return res[0]
            return None

        def _get_msg_name(obj):
            if obj.from_addr:
                contact_id = _get_addr_contact(obj.from_addr)
                if contact_id:
                    return "contact,%d" % contact_id
            if obj.to_addrs:
                for addr in obj.to_addrs.split(","):
                    addr = addr.strip()
                    contact_id = _get_addr_contact(addr)
                    if contact_id:
                        return "contact,%d" % contact_id
            if obj.cc_addrs:
                for addr in obj.cc_addrs.split(","):
                    addr = addr.strip()
                    contact_id = _get_addr_contact(addr)
                    if contact_id:
                        return "contact,%d" % contact_id

        def _get_msg_related(obj):
            m = re.search("\[(.*?)\]", obj.subject or "")
            if not m:
                return None
            num = m.group(1).strip()
            res = get_model("sale.opportunity").search(
                [["name", "=ilike", num]])
            if res:
                return "sale.opportunity,%s" % res[0]
            res = get_model("sale.quot").search([["number", "=ilike", num]])
            if res:
                return "sale.quot,%s" % res[0]
            res = get_model("sale.order").search([["number", "=ilike", num]])
            if res:
                return "sale.order,%s" % res[0]
            res = get_model("job").search([["number", "=ilike", num]])
            if res:
                return "job,%s" % res[0]
            res = get_model("account.invoice").search(
                [["number", "=ilike", num]])
            if res:
                return "account.invoice,%s" % res[0]
            return None

        for obj in self.browse(ids):
            if not obj.name_id:
                name_id = _get_msg_name(obj)
                if name_id:
                    obj.write({"name_id": name_id})
            if not obj.related_id:
                related_id = _get_msg_related(obj)
                if related_id:
                    obj.write({"related_id": related_id})

    def copy_to_lead(self, context={}):
        trigger_ids = context[
            "trigger_ids"]  # XXX: put in workflow action args instead (need eval)?
        obj = self.browse(trigger_ids)[0]
        vals = {
            "last_name": "/",
            "email": obj.from_addr,
            "description": obj.body,
        }
        lead_id = get_model("sale.lead").create(vals)
        obj.write({"related_id": "sale.lead,%d" % lead_id})

    def check_reject_list(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            rejected = False
            for addr in obj.to_addrs.split(","):
                addr = addr.strip()
                res = get_model("email.reject").search([["email", "=", addr]])
                if res:
                    rejected = True
                    break
            vals[obj.id] = rejected
        return vals

    def create_from_string(self,
                           email_str,
                           mailbox_id=None,
                           mailbox_message_uid=None):
        print("email.message create_from_string (%d bytes)" % len(email_str),
              mailbox_message_uid)
        msg = email.message_from_bytes(email_str)
        msg_id = msg.get("message-id")
        print("msg_id", msg_id)
        res = get_model("email.message").search(
            [["mailbox_id", "=", mailbox_id], ["message_id", "=", msg_id]])
        if res:
            return res[0]

        def dec_header(data):
            dh = decode_header(data or "")
            s = ""
            for data, charset in dh:
                if isinstance(data, str):
                    s += data
                else:
                    try:
                        s += data.decode(conv_charset(charset) or "utf-8")
                    except:
                        s += data.decode("utf-8", errors="replace")
            return s

        def dec_date(data):
            print("dec_date", data)
            res = parsedate_tz(data or "")
            if not res:
                return time.strftime("%Y-%m-%d %H:%M:%S")
            d = datetime.fromtimestamp(email.utils.mktime_tz(res))
            return d.strftime("%Y-%m-%d %H:%M:%S")

        body_text = []
        body_html = []
        attachments = []
        content_ids = {}
        for i, part in enumerate(msg.walk()):
            print("-" * 80)
            print("part #%d" % i)
            c_type = part.get_content_type()
            print("c_type", c_type)
            c_disp = part.get("Content-Disposition")
            print("c_disp", c_disp)
            c_id = part.get("Content-ID")
            print("c_id", c_id)
            if c_disp:
                fname = part.get_filename()
                data = part.get_payload(decode=True)
                attachments.append((fname, data))
            elif c_id:
                data = part.get_payload(decode=True)
                rand = base64.urlsafe_b64encode(os.urandom(8)).decode()
                fname = "email-inline-content," + rand
                if c_type == "image/png":
                    ext = ".png"
                elif c_type == "image/jpeg":
                    ext = ".jpg"
                elif c_type == "image/gif":
                    ext = ".gif"
                else:
                    ext = None
                if ext:
                    fname += "." + ext
                content_ids[c_id] = (fname, data)
            else:
                if c_type == "text/plain":
                    v = part.get_payload(decode=True).decode(errors="replace")
                    body_text.append(v)
                elif c_type == "text/html":
                    v = part.get_payload(decode=True).decode(errors="replace")
                    body_html.append(v)
                else:
                    continue
        if body_html:
            body = body_html[0]
        elif body_text:
            body = "<html><body>"
            for t in body_text:
                body += "<pre>" + t + "</pre>"
            body += "</body></html>"
        else:
            body = ""

        def _replace(m):
            cid = m.group(1)
            cid = "<" + cid + ">"
            if cid not in content_ids:
                return m.group(0)
            fname = content_ids[cid][0]
            dbname = get_active_db()
            return "/static/db/" + dbname + "/files/" + fname

        body = re.sub('"cid:(.*?)"', _replace, body)
        email_vals = {
            "type": "in",
            "state": "received",
            "date": dec_date(msg["date"]),
            "from_addr": parseaddr(msg["from"])[1][:64],
            "subject": dec_header(msg["subject"])[:128],
            "body": body,
            "message_id": msg_id,
            "parent_uid": msg.get("in-reply-to"),
            "mailbox_id": mailbox_id,
            "mailbox_message_uid": mailbox_message_uid,
        }
        if msg.get_all("to"):
            email_vals["to_addrs"] = ",".join(
                [parseaddr(x)[1] for x in msg.get_all("to")])[:256]
        if msg.get_all("cc"):
            email_vals["cc_addrs"] = ",".join(
                [parseaddr(x)[1] for x in msg.get_all("cc")])[:256]
        email_id = get_model("email.message").create(email_vals)
        for fname, data in attachments:
            if not fname:
                continue
            rand = base64.urlsafe_b64encode(os.urandom(8)).decode()
            res = os.path.splitext(fname)
            fname2 = res[0] + "," + rand + res[1]
            dbname = get_active_db()
            fdir = os.path.join("static", "db", dbname, "files")
            if not os.path.exists(fdir):
                os.makedirs(fdir)
            path = os.path.join(fdir, fname2)
            open(path, "wb").write(data)
            vals = {
                "email_id": email_id,
                "file": fname2,
            }
            get_model("email.attach").create(vals)
        for fname, data in content_ids.values():
            dbname = get_active_db()
            fdir = os.path.join("static", "db", dbname, "files")
            if not os.path.exists(fdir):
                os.makedirs(fdir)
            path = os.path.join(fdir, fname)
            open(path, "wb").write(data)

    def reply(self, ids, context={}):
        obj = self.browse(ids)[0]
        return {
            "next": {
                "name": "email",
                "mode": "form",
                "defaults": {
                    "parent_id": obj.id,
                },
            }
        }

    def get_open_detect(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.mailbox_id and obj.mailbox_id.type == "in":
                obj.write({"opened": True})
            vals[obj.id] = None
        return vals

    def send(self, ids, context={}):
        print("EmailMessage.send", ids)
        for obj in self.browse(ids):
            if obj.state not in ("draft", "to_send"):
                continue
            mailbox = obj.mailbox_id
            if not mailbox:
                raise Exception("Missing mailbox in email %s" % obj.id)
            account = mailbox.account_id
            if not account:
                raise Exception("Missing account in mailbox %s of email %s" %
                                (mailbox.name, obj.id))
            if obj.in_reject_list:
                print("WARNING: email in reject list: %s" % obj.id)
                obj.write({"state": "rejected"})
                continue
            if account.type == "smtp":
                obj.send_email_smtp()
            elif account.type == "mailgun":
                obj.send_email_mailgun()
            else:
                raise Exception("Invalid email account type")
        return {
            "next": {
                "name": "email",
                "tab_no": 1,
            },
        }

    def send_email_smtp(self, ids, context={}):
        print("send_email_smtp", ids)
        obj = self.browse(ids)[0]
        if obj.state not in ("draft", "to_send"):
            return
        try:
            mailbox = obj.mailbox_id
            if not mailbox:
                raise Exception("Missing mailbox")
            account = mailbox.account_id
            if account.type != "smtp":
                raise Exception("Invalid email account type")
            if account.security == "SSL":
                server = smtplib.SMTP_SSL(account.host,
                                          account.port or 465,
                                          timeout=30)
            else:
                server = smtplib.SMTP(account.host,
                                      account.port or 587,
                                      timeout=30)
            server.ehlo()
            if account.security == "starttls":
                server.starttls()
            if account.user:
                server.login(account.user, account.password)
            msg = MIMEMultipart()
            msg.set_charset("utf-8")
            msg["From"] = obj.from_addr
            msg["To"] = obj.to_addrs
            if obj.cc_addrs:
                msg["Cc"] = obj.cc_addrs
            msg["Subject"] = Header(obj.subject, "utf-8")
            msg.attach(MIMEText(obj.body, "html", "utf-8"))
            for attach in obj.attachments:
                path = utils.get_file_path(attach.file)
                data = open(path, "rb").read()
                part = MIMEBase('application', "octet-stream")
                part.set_payload(data)
                encode_base64(part)
                part.add_header('Content-Disposition',
                                'attachment; filename="%s"' % attach.file)
                msg.attach(part)
            to_addrs = obj.to_addrs.split(",")
            cc_addrs = obj.cc_addrs.split(",") if obj.cc_addrs else []
            server.sendmail(obj.from_addr, to_addrs + cc_addrs,
                            msg.as_string())
            obj.write({"state": "sent"})
            server.quit()
        except Exception as e:
            print("WARNING: failed to send email %s" % obj.id)
            import traceback
            traceback.print_exc()
            obj.write({"state": "error", "error_message": str(e)})

    def send_email_mailgun(self, ids, context={}):
        print("send_emails_mailgun", ids)
        obj = self.browse(ids)[0]
        if obj.state not in ("draft", "to_send"):
            return
        try:
            mailbox = obj.mailbox_id
            if not mailbox:
                raise Exception("Missing mailbox")
            account = mailbox.account_id
            if account.type != "mailgun":
                raise Exception("Invalid email account type")
            url = "https://api.mailgun.net/v2/%s/messages" % account.user
            to_addrs = []
            for a in obj.to_addrs.split(","):
                a = a.strip()
                if not utils.check_email_syntax(a):
                    raise Exception("Invalid email syntax: %s" % a)
                to_addrs.append(a)
            if not to_addrs:
                raise Exception("Missing recipient address")
            data = {
                "from": obj.from_addr,
                "to": to_addrs,
                "subject": obj.subject,
            }
            if obj.cc_addrs:
                data["cc"] = [a.strip() for a in obj.cc_addrs.split(",")]
            data["html"] = obj.body or "<html><body></body></html>"
            files = []
            for attach in obj.attachments:
                path = utils.get_file_path(attach.file)
                f = open(path, "rb")
                files.append(("attachment", f))
            r = requests.post(url,
                              auth=("api", account.password),
                              data=data,
                              files=files,
                              timeout=15)
            try:
                res = json.loads(r.text)
                msg_id = res["id"]
            except:
                raise Exception("Invalid mailgun response: %s" % r.text)
            obj.write({"state": "sent", "message_id": msg_id})
        except Exception as e:
            print("WARNING: failed to send email %s" % obj.id)
            import traceback
            traceback.print_exc()
            obj.write({"state": "error", "error_message": str(e)})

    def get_emails(self, context={}):
        print("EmailMessage.get_emails")
        for mailbox in get_model("email.mailbox").search_browse([]):
            acc = mailbox.account_id
            if not acc:
                continue
            if acc.type == "imap":
                mailbox.fetch_emails_imap()
                mailbox.get_flags_imap()
            elif acc.type == "pop":
                mailbox.fetch_emails_pop()
            elif acc.type == "mailgun":
                mailbox.get_email_events_mailgun()
        return {
            "next": {
                "name": "email",
                "tab_no": 0,
            },
        }

    def mark_opened(self, ids, context={}):
        self.write(ids, {"opened": True})

    def get_num_attach(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = len(obj.attachments)  # XXX: speed
        return vals
Exemple #27
0
class Cart(Model):
    _name = "ecom2.cart"
    _string = "Cart"
    _name_field = "number"
    _audit_log = True
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "date":
        fields.DateTime("Date Created", required=True, search=True),
        "customer_id":
        fields.Many2One("contact", "Customer", search=True),
        "lines":
        fields.One2Many("ecom2.cart.line", "cart_id", "Lines"),
        "ship_amount_details":
        fields.Json("Shipping Amount Details",
                    function="get_ship_amount_details"),
        "amount_ship":
        fields.Decimal("Shipping Amount", function="get_amount_ship"),
        "amount_total":
        fields.Decimal("Total Amount", function="get_total"),
        "sale_orders":
        fields.One2Many("sale.order", "related_id", "Sales Orders"),
        "delivery_date":
        fields.Date("Delivery Date"),
        "ship_address_id":
        fields.Many2One("address", "Shipping Address"),
        "bill_address_id":
        fields.Many2One("address", "Billing Address"),
        "delivery_slot_id":
        fields.Many2One("delivery.slot", "Peferred Delivery Slot"),
        "ship_method_id":
        fields.Many2One("ship.method", "Shipping Method"),
        "pay_method_id":
        fields.Many2One("payment.method", "Payment Method"),
        "logs":
        fields.One2Many("log", "related_id", "Audit Log"),
        "state":
        fields.Selection([["draft", "Draft"], ["confirmed", "Confirmed"],
                          ["canceled", "Canceled"]],
                         "Status",
                         required=True),
        "payment_methods":
        fields.Json("Payment Methods", function="get_payment_methods"),
        "delivery_delay":
        fields.Integer("Delivery Delay (Days)", function="get_delivery_delay"),
        "delivery_slots":
        fields.Json("Delivery Slots", function="get_delivery_slots"),
        "delivery_slots_str":
        fields.Text("Delivery Slots", function="get_delivery_slots_str"),
        "date_delivery_slots":
        fields.Json("Date Delivery Slots", function="get_date_delivery_slots"),
        "comments":
        fields.Text("Comments"),
        "transaction_no":
        fields.Char("Payment Transaction No.", search=True),
        "currency_id":
        fields.Many2One("currency", "Currency", required=True),
        "invoices":
        fields.One2Many("account.invoice", "related_id", "Invoices"),
        "company_id":
        fields.Many2One("company", "Company"),
        "voucher_id":
        fields.Many2One("sale.voucher", "Voucher"),
        "ship_addresses":
        fields.Json("Shipping Addresses", function="get_ship_addresses"),
        "amount_voucher":
        fields.Decimal("Voucher Amount",
                       function="get_amount_voucher",
                       function_multi=True),
        "voucher_error_message":
        fields.Text("Voucher Error Message",
                    function="get_amount_voucher",
                    function_multi=True),
        "free_ship_address":
        fields.Boolean("Is free Ship"),
    }
    _order = "date desc"

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence(type="ecom_cart")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            if not num:
                return None
            user_id = access.get_active_user()
            access.set_active_user(1)
            res = self.search([["number", "=", num]])
            access.set_active_user(user_id)
            if not res:
                return num
            get_model("sequence").increment_number(seq_id, context=context)

    def _get_currency(self, context={}):
        res = get_model("company").search([])  # XXX
        if not res:
            return
        company_id = res[0]
        access.set_active_company(company_id)
        settings = get_model("settings").browse(1)
        return settings.currency_id.id

    def _get_company(self, context={}):
        res = get_model("company").search([])  # XXX
        if res:
            return res[0]

    def _get_ship_method(self, context={}):
        res = get_model("ship.method").search([], order="sequence")
        if res:
            return res[0]

    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "number": _get_number,
        "state": "draft",
        "currency_id": _get_currency,
        "company_id": _get_company,
        "ship_method_d": _get_ship_method,
        "free_ship_address": False,
    }

    def get_ship_amount_details(self, ids, context={}):
        print("get_ship_amount_details", ids)
        vals = {}
        for obj in self.browse(ids):
            delivs = []
            for line in obj.lines:
                date = line.delivery_date
                meth_id = line.ship_method_id.id or line.cart_id.ship_method_id.id
                addr_id = line.ship_address_id.id or line.cart_id.ship_address_id.id
                if not date or not meth_id or not addr_id:
                    continue
                delivs.append((date, meth_id, addr_id))
            delivs = list(set(delivs))
            details = []
            for date, meth_id, addr_id in delivs:
                ctx = {
                    "contact_id": obj.customer_id.id,
                    "ship_address_id": addr_id,
                }
                meth = get_model("ship.method").browse(meth_id, context=ctx)
                details.append({
                    "ship_addr_id": addr_id,
                    "date": date,
                    "ship_method_id": meth.id,
                    "ship_amount": meth.ship_amount,
                })
            vals[obj.id] = details
        return vals

    def get_amount_ship(self, ids, context={}):
        print("get_amount_ship", ids)
        vals = {}
        for obj in self.browse(ids):
            ship_amt = 0
            for d in obj.ship_amount_details:
                ship_amt += d["ship_amount"] or 0
            vals[obj.id] = ship_amt
        return vals

    def get_total(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = 0
            for line in obj.lines:
                amt += line.amount
            vals[obj.id] = amt + obj.amount_ship - obj.amount_voucher
        return vals

    def get_payment_methods(self, ids, context={}):
        res = []
        for obj in get_model("payment.method").search_browse([]):
            res.append({
                "id": obj.id,
                "name": obj.name,
            })
        return {ids[0]: res}

    def confirm(self, ids, context={}):
        obj = self.browse(ids[0])
        user_id = context.get("user_id")  # XX: remove this
        if user_id:
            user_id = int(user_id)
            user = get_model("base.user").browse(user_id)
            if user.contact_id:
                obj.write({"customer_id": user.contact_id.id})
        access.set_active_company(1)  # XXX
        vals = {
            "contact_id": obj.customer_id.id,
            "ship_address_id": obj.ship_address_id.id,
            "ship_method_id": obj.ship_method_id.id,
            "bill_address_id": obj.bill_address_id.id,
            "due_date": obj.delivery_date,
            "lines": [],
            "related_id": "ecom2.cart,%s" % obj.id,
            "delivery_slot_id": obj.delivery_slot_id.id,
            "pay_method_id": obj.pay_method_id.id,
            "other_info": obj.comments,
            "voucher_id": obj.voucher_id.id,
            "ref": obj.comments,  # XXX
        }
        print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Ship Method Id ",
              ship_method_id)
        for line in obj.lines:
            prod = line.product_id
            if line.lot_id and line.qty_avail <= 0:
                raise Exception("Lot is out of stock (%s)" % prod.name)
            if not prod.locations:
                raise Exception("Can't find location for product %s" %
                                prod.code)
            location_id = prod.locations[0].location_id.id
            line_vals = {
                "product_id": prod.id,
                "description": prod.description,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price,
                "location_id": location_id,
                "lot_id": line.lot_id.id,
                "due_date": line.delivery_date,
                "delivery_slot_id": obj.delivery_slot_id.id,
                "ship_address_id": line.ship_address_id.id,
            }
            vals["lines"].append(("create", line_vals))
        for ship in obj.ship_amount_details:
            meth_id = ship["ship_method_id"]
            amount = ship["ship_amount"]
            meth = get_model("ship.method").browse(meth_id)
            prod = meth.product_id
            if not prod:
                raise Exception("Missing product in shipping method %s" %
                                meth.name)
            line_vals = {
                "product_id": prod.id,
                "description": prod.description,
                "qty": 1,
                "uom_id": prod.uom_id.id,
                "unit_price": amount,
            }
            vals["lines"].append(("create", line_vals))
        sale_id = get_model("sale.order").create(vals)
        sale = get_model("sale.order").browse(sale_id)
        sale.confirm()
        obj.write({"state": "confirmed"})
        obj.trigger("confirm_order")
        return {
            "sale_id": sale_id,
        }

    def cancel_order(self, ids, context={}):
        obj = self.browse(ids[0])
        for sale in obj.sale_orders:
            if sale.state == "voided":
                continue
            for inv in sale.invoices:
                if inv.state != "voided":
                    raise Exception(
                        "Can not cancel order %s because there are linked invoices"
                        % sale.number)
            for pick in sale.pickings:
                if pick.state == "voided":
                    continue
                pick.void()
            sale.void()
        for inv in obj.invoices:
            if inv.state != "voided":
                raise Exception(
                    "Can not cancel cart %s because there are linked invoices"
                    % obj.number)
        obj.write({"state": "canceled"})

    def payment_received(self, ids, context={}):
        obj = self.browse(ids[0])
        if obj.state != "waiting_payment":
            raise Exception("Cart is not waiting payment")
        res = obj.sale_orders.copy_to_invoice()
        inv_id = res["invoice_id"]
        inv = get_model("account.invoice").browse(inv_id)
        inv.write({"related_id": "ecom2.cart,%s" % obj.id})
        inv.post()
        method = obj.pay_method_id
        if not method:
            raise Exception("Missing payment method in cart %s" % obj.number)
        if not method.account_id:
            raise Exception("Missing account in payment method %s" %
                            method.name)
        transaction_no = context.get("transaction_no")
        pmt_vals = {
            "type": "in",
            "pay_type": "invoice",
            "contact_id": inv.contact_id.id,
            "account_id": method.account_id.id,
            "lines": [],
            "company_id": inv.company_id.id,
            "transaction_no": transaction_no,
        }
        line_vals = {
            "invoice_id": inv_id,
            "amount": inv.amount_total,
        }
        pmt_vals["lines"].append(("create", line_vals))
        pmt_id = get_model("account.payment").create(pmt_vals,
                                                     context={"type": "in"})
        get_model("account.payment").post([pmt_id])
        obj.write({"state": "paid"})
        for sale in obj.sale_orders:
            for pick in sale.pickings:
                if pick.state == "pending":
                    pick.approve()

    def to_draft(self, ids, context={}):
        obj = self.browse(ids[0])
        obj.write({"state": "draft"})

    def set_qty(self, ids, prod_id, qty, context={}):
        print("Cart.set_qty", ids, prod_id, qty)
        obj = self.browse(ids[0])
        line_id = None
        for line in obj.lines:
            if line.product_id.id == prod_id and not line.lot_id:
                line_id = line.id
                break
        if line_id:
            if qty == 0:
                get_model("ecom2.cart.line").delete([line_id])
            else:
                get_model("ecom2.cart.line").write([line_id], {"qty": qty})
        else:
            if qty != 0:
                get_model("ecom2.cart.line").create({
                    "cart_id": obj.id,
                    "product_id": prod_id,
                    "qty": qty
                })

    def set_date_qty(self, ids, due_date, prod_id, qty, context={}):
        print("Cart.set_date_qty", ids, due_date, prod_id, qty)
        obj = self.browse(ids[0])
        line_id = None
        for line in obj.lines:
            if line.delivery_date == due_date and line.product_id.id == prod_id and not line.lot_id:
                line_id = line.id
                break
        if line_id:
            if qty == 0:
                get_model("ecom2.cart.line").delete([line_id])
            else:
                get_model("ecom2.cart.line").write([line_id], {"qty": qty})
        else:
            if qty != 0:
                ctx = {
                    "cart_id": obj.id,
                    "delivery_date": due_date,
                    "product_id": prod_id
                }
                get_model("ecom2.cart.line").create(
                    {
                        "cart_id": obj.id,
                        "product_id": prod_id,
                        "qty": qty,
                        "delivery_date": due_date
                    },
                    context=ctx)

    def add_lot(self, ids, prod_id, lot_id, context={}):
        print("Cart.add_lot", ids, prod_id, lot_id)
        obj = self.browse(ids[0])
        line_id = None
        for line in obj.lines:
            if line.product_id.id == prod_id and line.lot_id.id == lot_id:
                line_id = line.id
                break
        if line_id:
            raise Exception("Lot already added to cart")
        get_model("ecom2.cart.line").create({
            "cart_id": obj.id,
            "product_id": prod_id,
            "lot_id": lot_id,
            "qty": 1
        })

    def remove_lot(self, ids, prod_id, lot_id, context={}):
        obj = self.browse(ids[0])
        line_id = None
        for line in obj.lines:
            if line.product_id.id == prod_id and line.lot_id.id == lot_id:
                line_id = line.id
                break
        if not line_id:
            raise Exception("Lot not found in cart")
        get_model("ecom2.cart.line").delete([line_id])

    def set_qty_auto_select_lot(self, ids, prod_id, qty, context={}):
        print("Cart.set_qty_auto_select_lot", ids, prod_id, qty)
        obj = self.browse(ids[0])
        cur_qty = 0
        for line in obj.lines:
            if line.product_id.id == prod_id:
                cur_qty += line.qty
        diff_qty = qty - cur_qty
        if diff_qty > 0:
            self.add_lots_auto_select(ids, prod_id, diff_qty, context=context)
        elif diff_qty < 0:
            self.remove_lots_auto_select(ids,
                                         prod_id,
                                         -diff_qty,
                                         context=context)

    def add_lots_auto_select(self, ids, prod_id, add_qty, context={}):
        print("Cart.add_lots_auto_select", ids, prod_id, add_qty)
        obj = self.browse(ids[0])
        exclude_lot_ids = []
        for line in obj.lines:
            if line.product_id.id == prod_id and line.lot_id:
                exclude_lot_ids.append(line.lot_id.id)
        prod = get_model("product").browse(prod_id)
        avail_qty = prod.stock_qty
        add_lot_ids = []
        for lot in prod.stock_lots:
            if len(add_lot_ids) >= avail_qty:
                break
            if lot.id not in exclude_lot_ids:
                add_lot_ids.append(lot.id)
                if len(add_lot_ids) >= add_qty:
                    break
        print("add_lot_ids", add_lot_ids)
        for lot_id in add_lot_ids:
            get_model("ecom2.cart.line").create({
                "cart_id": obj.id,
                "product_id": prod_id,
                "lot_id": lot_id,
                "qty": 1
            })
        remain_qty = add_qty - len(add_lot_ids)
        print("remain_qty", remain_qty)
        if remain_qty > 0:
            found_line = None
            for line in obj.lines:
                if line.product_id.id == prod_id and not line.lot_id:
                    found_line = line
                    break
            if found_line:
                found_line.write({"qty": found_line.qty + remain_qty})
            else:
                get_model("ecom2.cart.line").create({
                    "cart_id": obj.id,
                    "product_id": prod_id,
                    "qty": remain_qty
                })

    def remove_lots_auto_select(self, ids, prod_id, remove_qty, context={}):
        print("Cart.remove_lots_auto_select", ids, prod_id, remove_qty)
        obj = self.browse(ids[0])
        remain_qty = remove_qty
        for line in obj.lines:
            if line.product_id.id == prod_id and not line.lot_id:
                if line.qty <= remain_qty:
                    remain_qty -= line.qty
                    line.delete()
                else:
                    line.write({"qty": line.qty - remain_qty})
                    remain_qty = 0
        print("remain_qty", remain_qty)
        if remain_qty > 0:
            lots = []
            for line in obj.lines:
                if line.product_id.id == prod_id and line.lot_id:
                    d = line.lot_id.received_date or "1900-01-01"
                    lots.append((d, line.id))
            lots.sort()
            del_ids = []
            for d, line_id in lots:
                del_ids.append(line_id)
                if len(del_ids) >= remain_qty:
                    break
            print("del_ids", del_ids)
            get_model("ecom2.cart.line").delete(del_ids)

    def get_delivery_delay(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = max(l.delivery_delay
                               for l in obj.lines) if obj.lines else 0
        return vals

    def get_delivery_slots(self, ids, context={}):
        print("get_delivery_slots", ids)
        obj = self.browse(ids[0])
        settings = get_model("ecom2.settings").browse(1)
        max_days = settings.delivery_max_days
        if not max_days:
            return {obj.id: []}
        min_hours = settings.delivery_min_hours or 0
        d_from = date.today() + timedelta(days=obj.delivery_delay)
        d_to = d_from + timedelta(days=max_days)
        d = d_from
        slots = []
        for slot in get_model("delivery.slot").search_browse([]):
            slots.append([slot.id, slot.name, slot.time_from])
        slot_num_sales = {}
        for sale in get_model("sale.order").search_browse(
            [["plr_order_type", "=", "grocery"],
             ["date", ">=", time.strftime("%Y-%m-%d")]]):
            k = (sale.due_date, sale.delivery_slot_id.id)
            slot_num_sales.setdefault(k, 0)
            slot_num_sales[k] += 1
        print("slot_num_sales", slot_num_sales)
        slot_caps = {}
        for cap in get_model("delivery.slot.capacity").search_browse([]):
            k = (cap.slot_id.id, int(cap.weekday))
            slot_caps[k] = cap.capacity
        print("slot_caps", slot_caps)
        delivery_weekdays = None
        for line in obj.lines:
            prod = line.product_id
            if prod.delivery_weekdays:
                days = [int(w) for w in prod.delivery_weekdays.split(",")]
                if delivery_weekdays == None:
                    delivery_weekdays = days
                else:
                    delivery_weekdays = [
                        d for d in delivery_weekdays if d in days
                    ]
        days = []
        now = datetime.now()
        tomorrow = now + timedelta(days=1)
        tomorrow_seconds = 0
        if settings.work_time_start and settings.work_time_end:
            today_end = datetime.strptime(
                date.today().strftime("%Y-%m-%d") + " " +
                settings.work_time_end, "%Y-%m-%d %H:%M")
            tomorrow_start = datetime.strptime(
                tomorrow.strftime("%Y-%m-%d") + " " + settings.work_time_start,
                "%Y-%m-%d %H:%M")
            if now < today_end and now.weekday() != 6:
                remain_seconds = (today_end - now).total_seconds()
            else:
                remain_seconds = 0
            if remain_seconds < min_hours * 3600:
                tomorrow_seconds = min_hours * 3600 - remain_seconds
        print("tomorrow_seconds=%s" % tomorrow_seconds)
        while d <= d_to:
            ds = d.strftime("%Y-%m-%d")
            res = get_model("hr.holiday").search([["date", "=", ds]])
            if res:
                d += timedelta(days=1)
                continue
            w = d.weekday()
            if w == 6 or delivery_weekdays is not None and w not in delivery_weekdays:
                d += timedelta(days=1)
                continue
            day_slots = []
            for slot_id, slot_name, from_time in slots:
                t_from = datetime.strptime(ds + " " + from_time + ":00",
                                           "%Y-%m-%d %H:%M:%S")
                capacity = slot_caps.get((slot_id, w))
                num_sales = slot_num_sales.get((ds, slot_id), 0)
                state = "avail"
                if d == now.date():
                    if t_from < now or (t_from - now).total_seconds(
                    ) < min_hours * 3600 or tomorrow_seconds:
                        state = "full"
                elif d == tomorrow.date() and tomorrow_seconds:
                    if (t_from -
                            tomorrow_start).total_seconds() < tomorrow_seconds:
                        state = "full"
                if capacity is not None and num_sales >= capacity:
                    state = "full"
                day_slots.append(
                    [slot_id, slot_name, state, num_sales, capacity])
            days.append([ds, day_slots])
            d += timedelta(days=1)
        print("days:")
        pprint(days)
        return {obj.id: days}

    def get_delivery_slots_str(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            s = ""
            for d, slots in obj.delivery_slots:
                s += "- Date: %s\n" % d
                for slot_id, name, state, num_sales, capacity in slots:
                    s += "    - %s: %s (%s/%s)\n" % (name, state, num_sales,
                                                     capacity or "-")
            vals[obj.id] = s
        return vals

    def get_date_delivery_slots(self, ids, context={}):
        print("get_date_delivery_slots", ids)
        obj = self.browse(ids[0])
        slots = []
        for slot in get_model("delivery.slot").search_browse([]):
            slots.append([slot.id, slot.name])
        dates = []
        for line in obj.lines:
            d = line.delivery_date
            if d:
                dates.append(d)
        dates = list(set(dates))
        date_slots = {}
        for d in dates:
            date_slots[d] = slots  # TODO: use capacity?
        return {obj.id: date_slots}

    def get_ship_addresses(self, ids, context={}):
        obj = self.browse(ids[0])
        settings = get_model("ecom2.settings").browse(1)
        contact = obj.customer_id
        addrs = []
        if contact:
            for a in contact.addresses:
                addr_vals = {
                    "id": a.id,
                    "name": a.address,
                }
                if obj.ship_method_id:  # TODO: handle general case for different shipping methods per order
                    meth_id = obj.ship_method_id.id
                    ctx = {"ship_address_id": a.id}
                    meth = get_model("ship.method").browse(meth_id,
                                                           context=ctx)
                    addr_vals["ship_amount"] = meth.ship_amount
                else:
                    addr_vals["ship_amount"] = 0
                addrs.append(addr_vals)
        for a in settings.extra_ship_addresses:
            addr_vals = {
                "id": a.id,
                "name": a.address or "",
            }
            if obj.ship_method_id:
                meth_id = obj.ship_method_id.id
                ctx = {"ship_address_id": a.id}
                meth = get_model("ship.method").browse(meth_id, context=ctx)
                addr_vals["ship_amount"] = meth.ship_amount
            else:
                addr_vals["ship_amount"] = 0
            addrs.append(addr_vals)
        return {obj.id: addrs}

    def apply_voucher_code(self, ids, voucher_code, context={}):
        obj = self.browse(ids[0])
        res = get_model("sale.voucher").search([["code", "=", voucher_code]])
        if not res:
            raise Exception("Invalid voucher code")
        voucher_id = res[0]
        obj.write({"voucher_id": voucher_id})

    def clear_voucher(self, ids, context={}):
        obj = self.browse(ids[0])
        obj.write({"voucher_id": None})

    def get_amount_voucher(self, ids, context={}):
        print("get_amount_voucher", ids)
        vals = {}
        for obj in self.browse(ids):
            voucher = obj.voucher_id
            if voucher:
                ctx = {
                    "contact_id": obj.customer_id.id,
                    "amount_total": 0,
                    "products": [],
                }
                for line in obj.lines:
                    ctx["amount_total"] += line.amount
                    ctx["products"].append({
                        "product_id": line.product_id.id,
                        "unit_price": line.unit_price,
                        "qty": line.qty,
                        "uom_id": line.uom_id.id,
                        "amount": line.amount,
                    })
                ctx["amount_total"] += obj.amount_ship
                res = voucher.apply_voucher(context=ctx)
                disc_amount = res.get("discount_amount", 0)
                error_message = res.get("error_message")
            else:
                disc_amount = 0
                error_message = None
            vals[obj.id] = {
                "amount_voucher": disc_amount,
                "voucher_error_message": error_message,
            }
        return vals

    def update_date_delivery(self, ids, date, vals, context={}):
        print("cart.update_date_delivery", ids, date, vals)
        obj = self.browse(ids[0])
        settings = get_model("ecom2.settings").browse(1)
        for line in obj.lines:
            if line.delivery_date == date:
                line.write(vals)
        #for a in settings.extra_ship_addresses:
        #if a.id == vals['ship_address_id']:
        #return {'free_ship':True}
        return {'free_ship': False}

    def empty_cart(self, ids, context={}):
        obj = self.browse(ids[0])
        obj.write({"lines": [("delete_all", )]})

    def check_due_dates(self, ids, context={}):
        obj = self.browse(ids[0])
        today = time.strftime("%Y-%m-%d")
        if obj.delivery_date and obj.delivery_date < today:
            raise Exception("Delivery date is in the past %s" %
                            obj.delivery_date)
        for line in obj.lines:
            if line.delivery_date and line.delivery_date < today:
                raise Exception(
                    "Delivery date is in the past %s for product %s" %
                    (line.delivery_date, line.product_id.name))

    def free_ship_address(self, ids, context={}):
        obj = self.browse(ids[0])
        settings = get_model("ecom2.settings").browse(1)
        data = []
        for a in settings.extra_ship_addresses:
            if obj.ship_method_id:
                data["is_free"] = False
            else:
                data["is_free"] = True
            data.append(data)
        return {obj.id: addrs}
Exemple #28
0
class StockLot(Model):
    _name = "stock.lot"
    _string = "Lot / Serial Number"
    _name_field = "number"
    _key = ["number"]
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "received_date":
        fields.DateTime("Received Date", search=True),
        "expiry_date":
        fields.Date("Expiry Date", search=True),
        "description":
        fields.Text("Description", search=True),
        "weight":
        fields.Decimal("Weight"),
        "width":
        fields.Decimal("Width"),
        "length":
        fields.Decimal("Length"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "product_id":
        fields.Many2One("product", "Product", search=True),
        "stock_balances":
        fields.One2Many("stock.balance", "lot_id", "Stock Quantities"),
        "service_item_id":
        fields.Many2One("service.item", "Service Item"),  # XXX: deprecated
    }
    _order = "number desc"

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence("stock_lot")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id)
            res = self.search([["number", "=", num]])
            if not res:
                return num
            get_model("sequence").increment_number(seq_id)

    _defaults = {
        "number": _get_number,
    }

    def remove_expired_lots(self, context={}):
        print("StockLot.remove_expired_lots")
        access.set_active_user(1)
        access.set_active_company(1)
        settings = get_model("settings").browse(1)
        if not settings.lot_expiry_journal_id:
            raise Exception("Missing lot expiry journal")
        journal = settings.lot_expiry_journal_id
        if not journal.location_to_id:
            raise Exception("Missing to location in lot expiry journal")
        t = time.strftime("%Y-%m-%d")
        pick_vals = {
            "type": "out",
            "journal_id": journal.id,
            "lines": [],
        }
        n = 0
        for obj in self.search_browse([["expiry_date", "<", t]]):
            prod = obj.product_id
            if not prod:
                continue
            for bal in obj.stock_balances:
                if bal.qty_phys <= 0:
                    continue
                line_vals = {
                    "product_id": prod.id,
                    "location_from_id": bal.location_id.id,
                    "location_to_id": journal.location_to_id.id,
                    "lot_id": obj.id,
                    "qty": bal.qty_phys,
                    "uom_id": prod.uom_id.id,
                }
                pick_vals["lines"].append(("create", line_vals))
                n += 1
        if pick_vals["lines"]:
            pick_id = get_model("stock.picking").create(
                pick_vals, context={"pick_type": "out"})
            get_model("stock.picking").set_done([pick_id])
            get_model("stock.picking").trigger([pick_id], "lot_expired")
        return {
            "flash": "%d lots removed from stock" % n,
        }
class Coupon(Model):
    _name = "sale.coupon"
    _string = "Coupon"
    _key = ["code"]
    _fields = {
        "master_id":
        fields.Many2One("sale.coupon.master",
                        "Coupon Master",
                        required=True,
                        search=True),
        "code":
        fields.Char("Code", required=True),
        "contact_id":
        fields.Many2One("contact", "Customer", required=True, search=True),
        "state":
        fields.Selection([["available", "Available"], ["in_use", "In Use"],
                          ["used", "Used"], ["expired", "Expired"]],
                         "Status",
                         required=True,
                         search=True),
        "active":
        fields.Boolean("Active", required=True),
        "use_date":
        fields.DateTime("Usage Date", readonly=True),
        "expiry_date":
        fields.DateTime("Expiry Date"),
        "use_duration":
        fields.Integer("Usability Duration (minutes)"),
        "hide_date":
        fields.DateTime("Hide Date"),
        "contact_email":
        fields.Char("Contact Email",
                    function="_get_related",
                    function_search="_search_related",
                    function_context={"path": "contact_id.email"},
                    search=True),
    }

    def _get_code(self, context={}):
        while 1:
            code = "%.3d" % random.randint(0, 999)
            code += "-"
            code += "%.3d" % random.randint(0, 999)
            code += "-"
            code += "%.4d" % random.randint(0, 9999)
            if not get_model("sale.coupon").search([["code", "=", code]]):
                return code

    _defaults = {
        "state": "available",
        "active": True,
        "code": _get_code,
    }
    _order = "id desc"

    def use_coupon(self, ids, context={}):
        obj = self.browse(ids[0])
        if obj.state != "available":
            raise Exception("Invalid coupon status")
        t = datetime.now()
        if obj.expiry_date and t.strftime(
                "%Y-%m-%d %H:%M:%S") >= obj.expiry_date:
            raise Exception("Coupon is expired")
        vals = {
            "state": "in_use",
            "use_date": t.strftime("%Y-%m-%d %H:%M:%S"),
        }
        if obj.use_duration:
            t2 = t + timedelta(minutes=obj.use_duration)
            vals["expiry_date"] = t2.strftime("%Y-%m-%d %H:%M:%S")
        obj.write(vals)

    def update_coupons(self, context={}):
        t = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        ids = self.search([["state", "=", "available"],
                           ["expiry_date", "<=", t]])
        if ids:
            self.write(ids, {"state": "expired"})
        ids = self.search([["state", "=", "in_use"], ["expiry_date", "<=", t]])
        if ids:
            self.write(ids, {"state": "used"})
        ids = self.search([["active", "=", True], ["hide_date", "<=", t]])
        if ids:
            self.write(ids, {"active": False})

    #def get_report_data(self,ids,context={}):
    #objs = self.browse(ids)
    #for obj in objs:
    #if obj.state == "available":
    #t=datetime.now()
    #vals={
    #"state": "used",
    #"use_date": t.strftime("%Y-%m-%d %H:%M:%S"),
    #"expiry_date": t.strftime("%Y-%m-%d %H:%M:%S"),
    #}
    #obj.write(vals)
    #data = super().get_report_data(ids, context)
    #return data

    def create_individual_coupon(self, master_ids=[], context={}):
        contact_ids = context.get("trigger_ids")
        for contact_id in contact_ids:
            for master_id in master_ids:
                master = get_model("sale.coupon.master").browse(master_id)
                vals = {
                    "master_id": master_id,
                    "contact_id": contact_id,
                    "expiry_date": master.expiry_date,
                    "use_duration": master.use_duration,
                    "hide_date": master.hide_date,
                }
                self.create(vals)
Exemple #30
0
class Leave(Model):
    _name = "hr.leave"
    _string = "Leave Request"
    _multi_company = True
    _key = ["number"]

    _fields = {
        "name": fields.Char("Name", function="get_name"),
        "number": fields.Char("Number"),
        "date": fields.Date("Request Date", required=True),
        "employee_id": fields.Many2One("hr.employee", "Employee", required=True),
        "employee_work_status": fields.Selection([["working", "Working"], ["dismissed", "Dismissed"], ["resigned", "Resigned"], ["died", "Died"]], "Work Status"),
        "leave_type_id": fields.Many2One("hr.leave.type", "Leave Type", required=True),
        "leave_reason": fields.Text("Leave Reason"),
        "start_date": fields.DateTime("Start Date", required=True),
        "end_date": fields.DateTime("End Date", required=True),
        "state": fields.Selection([["draft", "Draft"], ["waiting_approval", "Awaiting Approval"], ["approved", "Approved"], ["rejected", "Rejected"]], "Status"),
        "days_requested": fields.Decimal("Days Requested", function="cal_days_requested"),
        "days_remaining": fields.Decimal("Days Remaining", function="cal_days_remaining"),
        "documents": fields.One2Many("document", "related_id", "Documents"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "time_from": fields.Char("From Time"),
        "time_to": fields.Char("To Time"),
        "period_id": fields.Many2One("hr.leave.period", "Leave Period", required=True),
        'schedule_id': fields.Many2One("hr.schedule", "Working Schedule"),
        "user_id": fields.Many2One("base.user", "User"),
        "company_id": fields.Many2One("company", "Company"),
    }

    def _get_number(self, context={}):
        user_id = get_active_user()
        set_active_user(1)
        seq_id = get_model("sequence").find_sequence(type="leave_request")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id)
            user_id = get_active_user()
            set_active_user(1)
            res = self.search([["number", "=", num]])
            set_active_user(user_id)
            if not res:
                return num
            get_model("sequence").increment_number(seq_id)
        set_active_user(user_id)

    def _get_employee(self, context={}):
        user_id = get_active_user()
        res = get_model("hr.employee").search([["user_id", "=", user_id]])
        if not res:
            return None
        return res[0]

    _defaults = {
        "state": "draft",
        "date": lambda *a: time.strftime(FMT_DAY),
        "period_type": "day",
        "period_hour": 0,
        "number": _get_number,
        "employee_id": _get_employee,
        "company_id": lambda *a: get_active_company(),
        'start_date': lambda *a: '%s 08:30:00' % time.strftime(FMT_DAY),
        'end_date': lambda *a: '%s 18:00:00' % time.strftime(FMT_DAY),
        'user_id': lambda *a: get_active_user(),
    }

    _order = "date desc, number desc"

    def get_name(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            emp = obj.employee_id
            vals[obj.id] = "%s %s" % (emp.first_name, emp.last_name)
        return vals

    def get_holidays(self, ids, context={}):
        date_from = context.get('date_from', time.strftime(FMT_DAY))
        date_to = context.get('date_to', time.strftime(FMT_DAY))
        cond = [
            ['date', '>=', date_from],
            ['date', '<=', date_to],
        ]
        res = set()
        for r in get_model("hr.holiday").search_read(cond, ['date']):
            res.update({r['date']})
        return list(res)

    def daterange(self, start_date, end_date):
        # interate between start date and stop date
        for n in range(int((end_date - start_date).days)):
            yield start_date + timedelta(n)

    def get_day_request(self, data={}):
        day_off = [5, 6]  # Sat and Sun XXX
        default_hour = 8
        total_break = 0
        part1 = 0
        employee_id = data.get('employee_id')
        if not employee_id:
            return 0
        start_date = data.get('start_date')
        end_date = data.get('end_date')
        if not start_date or not end_date:
            return 0
        holidays = get_model("hr.holiday").get_holidays(context=data)
        emp = get_model("hr.employee").browse(employee_id)
        start_date = datetime.strptime(start_date, FMT_TIME)
        end_date = datetime.strptime(end_date, FMT_TIME)
        total_day_off = 0
        schedule = emp.schedule_id
        if data.get('schedule_id'):
            schedule = get_model("hr.schedule").browse(data['schedule_id'])
        if schedule:
            default_hour = schedule.working_hour or 0
            total_break = schedule.total_break or 0
            part1 = schedule.part1 or 0
            for rdate in self.daterange(start_date, end_date + timedelta(days=1)):
                hdate = rdate.strftime(FMT_DAY)
                if hdate in holidays:
                    total_day_off += 1
                dow = rdate.weekday()
                if dow in day_off:
                    total_day_off += 1
                else:
                    #items=schedule.get_day(context={'dow': dow})
                    pass
        total_day = 0
        total_hrs = 0
        total_day -= total_day_off
        if end_date >= start_date:
            diff = end_date - start_date
            total_day = diff.days
            total_day -= total_day_off
            total_sec = diff.seconds
            total_hrs = total_sec / 60 / 60
        if total_hrs >= part1 and total_hrs <= part1 + total_break and part1 > 0:
            hrs2day = Decimal(0.5)
        else:
            if total_hrs >= part1 + total_break * 2:  # XXX *2
                total_hrs -= total_break
            hrs2day = (total_hrs / default_hour)
        res = total_day + hrs2day
        return Decimal(res)

    def cal_days_requested(self, ids, context={}):
        res = {}
        for obj in self.browse(ids):
            emp = obj.employee_id
            data = {
                'employee_id': emp.id,
                'start_date': obj.start_date,
                'end_date': obj.end_date,
            }
            res[obj.id] = self.get_day_request(data)
        return res

    def cal_days_remaining(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            period_id = obj.period_id.id
            emp_id = obj.employee_id.id
            vals[obj.id] = self.get_remaining(emp_id, period_id)
        return vals

    def get_remaining(self, emp_id=None, period_id=None):
        if not emp_id or not period_id:
            return 0
        requested = self.search_read(
            [["period_id", "=", period_id], ["state", "=", "approved"], ["employee_id", "=", emp_id]], ["days_requested"])
        requested = sum([d.get("days_requested") or 0 for d in requested])
        if period_id:
            total = get_model("hr.leave.period").read([period_id], ["max_days"])[0].get("max_days") or 0
        else:
            total = 0
        return total - requested

    def submit_for_approval(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "draft":
                raise Exception("Invalid state")
            obj.write({"state": "waiting_approval"})
            obj.trigger("submit_approval")

    def approve(self, ids, context={}):
        user_id = get_active_user()
        user = get_model("base.user").browse(user_id)
        res = get_model("hr.employee").search([["user_id", "=", user_id]])
        if res:
            emp_id = res[0]
        for obj in self.browse(ids):
            if obj.state not in ("draft", "waiting_approval"):
                raise Exception("Invalid state")
            if res and obj.employee_id.id == emp_id:
                if obj.employee_id.approver_id:
                    raise Exception("User %s is not authorized to approve his own leave requests" % user.name)
            else:
                if obj.employee_id.approver_id.id != user_id:
                    raise Exception("User %s is not authorized to approve leave requests of employee %s" %
                                    (user.name, obj.employee_id.name_get()[0][1]))
            obj.write({"state": "approved"})
            vals = {
                "related_id": "hr.leave,%s" % obj.id,
                "body": "Approved by %s" % user.name,
            }
            get_model("message").create(vals)
            obj.trigger("approved")

    def reject(self, ids, context={}):
        user_id = get_active_user()
        user = get_model("base.user").browse(user_id)
        res = get_model("hr.employee").search([["user_id", "=", user_id]])
        if res:
            emp_id = res[0]
        for obj in self.browse(ids):
            if obj.state not in ("draft", "waiting_approval"):
                raise Exception("Invalid state")
            if res and obj.employee_id.id == emp_id:
                if obj.employee_id.approver_id:
                    raise Exception("User %s is not authorized to reject his own leave requests" % user.name)
            else:
                if obj.employee_id.approver_id.id != user_id:
                    raise Exception("User %s is not authorized to reject leave requests of employee %s" %
                                    (user.name, obj.employee_id.name_get()[0][1]))
            obj.write({"state": "rejected"})
            vals = {
                "related_id": "hr.leave,%s" % obj.id,
                "body": "Rejected by %s" % user.name,
            }
            get_model("message").create(vals)
            obj.trigger("rejected")

    def do_reopen(self, ids, context={}):
        user_id = get_active_user()
        user = get_model("base.user").browse(user_id)
        res = get_model("hr.employee").search([["user_id", "=", user_id]])
        if res:
            emp_id = res[0]
        for obj in self.browse(ids):
            assert obj.state in ("rejected", "approved")
            if res and obj.employee_id.id == emp_id:
                if obj.employee_id.approver_id:
                    raise Exception("User %s is not authorized to reopen his own leave requests" % user.name)
            else:
                if obj.employee_id.approver_id.id != user_id:
                    raise Exception("User %s is not authorized to reopen leave requests of employee %s" %
                                    (user.name, obj.employee_id.name_get()[0][1]))
            obj.write({"state": "waiting_approval"})

    def to_draft(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "draft"})

    def convert_date(self, date_in):
        date_out = [int(x) for x in date_in.split("-")]
        return date(date_out[0], date_out[1], date_out[2])

    def write(self, ids, vals, **kw):
        obj = self.browse(ids)[0]
        vals['employee_work_status'] = obj.employee_id.work_status
        super().write(ids, vals, **kw)

    def onchange_date(self, context={}):
        data = context['data']
        data['days_requested'] = self.get_day_request(data)
        return data

    def onchange_employee(self, context={}):
        data = context["data"]
        data["period_id"] = None
        data["leave_type_id"] = None
        data['days_remaining'] = None
        data['days_requested'] = None
        data['schedule_id'] = None
        employee_id = data['employee_id']
        schd = get_model("hr.employee").browse(employee_id).schedule_id
        if schd:
            data['schedule_id'] = schd.id
            datenow = datetime.now()
            wd = datenow.weekday()
            res = []
            for line in schd.lines:
                dow = int(line.dow or '0') - 1
                if wd == dow:
                    res.append({
                        'time_start': '%s:00' % line.time_start,
                        'time_stop': '%s:00' % line.time_stop,
                    })
            if res:
                data['start_date'] = '%s %s' % (datenow.strftime(FMT_DAY), res[0]['time_start'])
                data['end_date'] = '%s %s' % (datenow.strftime(FMT_DAY), res[-1]['time_stop'])
                data['days_requested'] = self.get_day_request(data)
        return data

    def onchange_type(self, context={}):
        data = context["data"]
        leave_type = data["leave_type_id"]
        con = "leave_type_id = %s" % (leave_type)
        year = datetime.now().strftime("%Y")
        db = database.get_connection()
        pids = db.query("SELECT id FROM hr_leave_period WHERE %s" % con)
        pids = [pid["id"] for pid in pids]
        sids = []
        for obj in get_model("hr.leave.period").browse(pids):
            if obj.get("date_to").find(year) != -1:
                sids.append(obj.id)
                data['days_remaining'] = obj.max_days or 0
        if sids:
            pid = sids[-1]
            data["period_id"] = pid
            if data.get("employee_id"):
                data['days_remaining'] = self.get_remaining(data['employee_id'], pid)
        else:
            data["period_id"] = None
        return data